User Management
User Management in XOOPS
Section titled “User Management in XOOPS”The XOOPS User Management system provides a complete framework for handling user registration, authentication, profile management, and user preferences. This document covers the user object structure, registration flows, and practical implementation examples.
User Object Structure
Section titled “User Object Structure”The core user object in XOOPS is the XoopsUser class, which encapsulates all user data and methods.
Database Schema
Section titled “Database Schema”CREATE TABLE xoops_users ( uid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, uname VARCHAR(25) NOT NULL UNIQUE, email VARCHAR(60) NOT NULL, pass VARCHAR(255) NOT NULL, pass_expired DATETIME DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, last_login DATETIME DEFAULT NULL, login_attempts INT(11) DEFAULT 0, user_avatar VARCHAR(255) NOT NULL DEFAULT 'blank.gif', user_regdate INT(11) NOT NULL DEFAULT 0, user_icq VARCHAR(15) NOT NULL DEFAULT '', user_from VARCHAR(100) NOT NULL DEFAULT '', user_sig TEXT, user_sig_smilies TINYINT(1) NOT NULL DEFAULT 1, user_viewemail TINYINT(1) NOT NULL DEFAULT 0, user_attachsig TINYINT(1) NOT NULL DEFAULT 0, user_theme VARCHAR(32) NOT NULL DEFAULT '', user_language VARCHAR(32) NOT NULL DEFAULT '', user_openid VARCHAR(255) NOT NULL DEFAULT '', user_notify_method TINYINT(1) NOT NULL DEFAULT 0, user_notify_interval INT(11) NOT NULL DEFAULT 0);XoopsUser Class Properties
Section titled “XoopsUser Class Properties”class XoopsUser{ protected $uid; protected $uname; protected $email; protected $pass; protected $pass_expired; protected $created_at; protected $updated_at; protected $last_login; protected $login_attempts; protected $user_avatar; protected $user_regdate; protected $user_icq; protected $user_from; protected $user_sig; protected $user_sig_smilies; protected $user_viewemail; protected $user_attachsig; protected $user_theme; protected $user_language; protected $user_openid; protected $user_notify_method; protected $user_notify_interval;}User Registration Flow
Section titled “User Registration Flow”Registration Sequence Diagram
Section titled “Registration Sequence Diagram”sequenceDiagram participant User as Web User participant Browser participant Server as XOOPS Server participant DB as Database participant Email as Email Service
User->>Browser: Fill Registration Form Browser->>Server: POST /register
Server->>Server: Validate Input note over Server: Check username,<br/>email, password<br/>requirements
alt Validation Fails Server-->>Browser: Show Errors else Validation Passes Server->>Server: Hash Password Server->>DB: Insert User Record DB-->>Server: Success (uid)
Server->>Server: Generate Verification Token Server->>Email: Send Activation Email Email-->>User: Activation Link
Server-->>Browser: Registration Confirmation
User->>Email: Click Activation Link Email-->>Browser: Redirect to Activate URL Browser->>Server: GET /activate?token=xxx
Server->>DB: Mark User as Active DB-->>Server: Confirmed
Server-->>Browser: Account Activated Browser-->>User: Show Success Message endRegistration Implementation
Section titled “Registration Implementation”<?php/** * User Registration Handler */class RegistrationHandler{ private $userHandler; private $configHandler;
public function __construct() { $this->userHandler = xoops_getHandler('user'); $this->configHandler = xoops_getHandler('config'); }
/** * Validate registration input * * @param array $data Registration form data * @return array Validation errors, empty if valid */ public function validateInput(array $data): array { $errors = [];
// Username validation if (empty($data['uname'])) { $errors[] = 'Username is required'; } elseif (strlen($data['uname']) < 3) { $errors[] = 'Username must be at least 3 characters'; } elseif (!preg_match('/^[a-zA-Z0-9_-]+$/', $data['uname'])) { $errors[] = 'Username contains invalid characters'; } elseif ($this->userHandler->getUserByName($data['uname'])) { $errors[] = 'Username already exists'; }
// Email validation if (empty($data['email'])) { $errors[] = 'Email is required'; } elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { $errors[] = 'Invalid email format'; } elseif ($this->userHandler->getUserByEmail($data['email'])) { $errors[] = 'Email already registered'; }
// Password validation if (empty($data['password'])) { $errors[] = 'Password is required'; } elseif (strlen($data['password']) < 8) { $errors[] = 'Password must be at least 8 characters'; } elseif ($data['password'] !== $data['password_confirm']) { $errors[] = 'Passwords do not match'; }
return $errors; }
/** * Register new user * * @param array $data Registration data * @return XoopsUser|false User object or false on failure */ public function registerUser(array $data) { // Validate input $errors = $this->validateInput($data); if (!empty($errors)) { return false; }
// Create user object $user = $this->userHandler->create(); $user->setVar('uname', $data['uname']); $user->setVar('email', $data['email']); $user->setVar('user_regdate', time());
// Hash password using bcrypt $hashedPassword = password_hash( $data['password'], PASSWORD_BCRYPT, ['cost' => 12] ); $user->setVar('pass', $hashedPassword);
// Set default preferences $user->setVar('user_theme', $this->configHandler->getConfig('default_theme')); $user->setVar('user_language', $this->configHandler->getConfig('default_language'));
// Save user if ($this->userHandler->insertUser($user)) { $uid = $user->getVar('uid');
// Generate verification token $token = bin2hex(random_bytes(32)); $this->saveVerificationToken($uid, $token);
// Send verification email $this->sendVerificationEmail($user, $token);
return $user; }
return false; }
/** * Save verification token * * @param int $uid User ID * @param string $token Verification token */ private function saveVerificationToken(int $uid, string $token): void { $tokenHandler = xoops_getHandler('usertoken'); $tokenHandler->saveToken($uid, $token, 'email_verification', 24); // 24 hours }
/** * Send verification email * * @param XoopsUser $user User object * @param string $token Verification token */ private function sendVerificationEmail(XoopsUser $user, string $token): void { global $xoopsConfig;
$siteUrl = $xoopsConfig['siteurl']; $activationUrl = $siteUrl . '/user.php?op=activate&token=' . $token;
$subject = 'Email Verification - ' . $xoopsConfig['sitename']; $body = "Hello " . $user->getVar('uname') . ",\n\n"; $body .= "Please click the link below to verify your email:\n"; $body .= $activationUrl . "\n\n"; $body .= "This link will expire in 24 hours.\n\n"; $body .= "Regards,\n" . $xoopsConfig['sitename'];
$mailHandler = xoops_getHandler('mail'); $mailHandler->send($user->getVar('email'), $subject, $body); }}User Authentication Process
Section titled “User Authentication Process”Authentication Flow Diagram
Section titled “Authentication Flow Diagram”graph TD A["Login Form"] --> B["Username/Email & Password"] B --> C{"Input Validation"} C -->|Invalid| D["Show Error"] C -->|Valid| E["Query User Database"] E --> F{"User Found?"} F -->|No| G["Invalid Credentials"] G --> H["Log Failed Attempt"] H --> I{"Max Attempts?"} I -->|Yes| J["Account Locked"] I -->|No| D F -->|Yes| K["Verify Password Hash"] K --> L{"Hash Match?"} L -->|No| H L -->|Yes| M["Check Account Status"] M --> N{"Account Active?"} N -->|No| O["Account Inactive/Suspended"] N -->|Yes| P["Create Session"] P --> Q["Store Session Token"] Q --> R{"Remember Me?"} R -->|Yes| S["Set Long-lived Cookie"] R -->|No| T["Set Session Cookie"] S --> U["Update Last Login"] T --> U U --> V["Redirect to Dashboard"]Authentication Implementation
Section titled “Authentication Implementation”<?php/** * Authentication Handler */class AuthenticationHandler{ private $userHandler; private $maxLoginAttempts = 5; private $lockoutDuration = 900; // 15 minutes
public function __construct() { $this->userHandler = xoops_getHandler('user'); }
/** * Authenticate user by username/email and password * * @param string $username Username or email * @param string $password Plain text password * @param bool $rememberMe Remember login * @return XoopsUser|false Authenticated user or false */ public function authenticate( string $username, string $password, bool $rememberMe = false ) { // Check account lockout if ($this->isAccountLocked($username)) { throw new Exception('Account temporarily locked due to failed login attempts'); }
// Find user by username or email $user = $this->userHandler->getUserByName($username); if (!$user) { $user = $this->userHandler->getUserByEmail($username); }
if (!$user) { $this->recordFailedAttempt($username); return false; }
// Verify password if (!password_verify($password, $user->getVar('pass'))) { $this->recordFailedAttempt($username); return false; }
// Check account status if ($user->getVar('level') == 0) { throw new Exception('Account is inactive'); }
// Clear failed attempts $this->clearFailedAttempts($user->getVar('uid'));
// Update last login $user->setVar('last_login', date('Y-m-d H:i:s')); $this->userHandler->insertUser($user);
// Create session $this->createSession($user, $rememberMe);
return $user; }
/** * Create authenticated session * * @param XoopsUser $user User object * @param bool $rememberMe Enable persistent login */ private function createSession(XoopsUser $user, bool $rememberMe = false): void { // Generate session token $token = bin2hex(random_bytes(32));
$_SESSION['xoopsUserId'] = $user->getVar('uid'); $_SESSION['xoopsUserName'] = $user->getVar('uname'); $_SESSION['xoopsSessionToken'] = $token; $_SESSION['xoopsSessionCreated'] = time();
// Store token in database for validation $this->storeSessionToken($user->getVar('uid'), $token);
if ($rememberMe) { // Create persistent login cookie (14 days) $cookieToken = bin2hex(random_bytes(32)); setcookie( 'xoops_persistent_login', $cookieToken, time() + (14 * 24 * 60 * 60), '/', '', true, // HTTPS only true // HttpOnly );
// Store cookie token hash $this->storePersistentToken( $user->getVar('uid'), hash('sha256', $cookieToken) ); } }
/** * Record failed login attempt * * @param string $username Username or email */ private function recordFailedAttempt(string $username): void { $key = 'login_attempt_' . md5($username); $attempts = apcu_fetch($key) ?: 0; apcu_store($key, $attempts + 1, $this->lockoutDuration); }
/** * Check if account is locked * * @param string $username Username or email * @return bool True if locked */ private function isAccountLocked(string $username): bool { $key = 'login_attempt_' . md5($username); $attempts = apcu_fetch($key) ?: 0; return $attempts >= $this->maxLoginAttempts; }
/** * Clear failed attempts * * @param int $uid User ID */ private function clearFailedAttempts(int $uid): void { $user = $this->userHandler->getUser($uid); $user->setVar('login_attempts', 0); $this->userHandler->insertUser($user); }
/** * Store session token * * @param int $uid User ID * @param string $token Session token */ private function storeSessionToken(int $uid, string $token): void { // Store in database or cache $tokenData = [ 'uid' => $uid, 'token' => hash('sha256', $token), 'created' => time(), 'expires' => time() + (8 * 60 * 60) // 8 hours ];
$db = XoopsDatabaseFactory::getDatabaseConnection(); $db->query("INSERT INTO xoops_sessions (uid, token, created, expires) VALUES (?, ?, ?, ?)", array($uid, $tokenData['token'], $tokenData['created'], $tokenData['expires'])); }}Profile Management
Section titled “Profile Management”Profile Update Implementation
Section titled “Profile Update Implementation”<?php/** * User Profile Management */class ProfileManager{ private $userHandler; private $avatarHandler;
public function __construct() { $this->userHandler = xoops_getHandler('user'); $this->avatarHandler = xoops_getHandler('avatar'); }
/** * Update user profile * * @param int $uid User ID * @param array $data Profile data * @return bool Success status */ public function updateProfile(int $uid, array $data): bool { $user = $this->userHandler->getUser($uid); if (!$user) { return false; }
// Update profile fields if (isset($data['email'])) { // Verify email is unique (excluding current user) $existingUser = $this->userHandler->getUserByEmail($data['email']); if ($existingUser && $existingUser->getVar('uid') !== $uid) { throw new Exception('Email already in use'); } $user->setVar('email', $data['email']); }
if (isset($data['user_icq'])) { $user->setVar('user_icq', sanitize_text_field($data['user_icq'])); }
if (isset($data['user_from'])) { $user->setVar('user_from', sanitize_text_field($data['user_from'])); }
if (isset($data['user_sig'])) { $sig = $data['user_sig']; if (strlen($sig) > 500) { throw new Exception('Signature too long'); } $user->setVar('user_sig', $sig); }
if (isset($data['user_sig_smilies'])) { $user->setVar('user_sig_smilies', (int)$data['user_sig_smilies']); }
if (isset($data['user_viewemail'])) { $user->setVar('user_viewemail', (int)$data['user_viewemail']); }
if (isset($data['user_attachsig'])) { $user->setVar('user_attachsig', (int)$data['user_attachsig']); }
if (isset($data['user_theme'])) { $user->setVar('user_theme', $data['user_theme']); }
if (isset($data['user_language'])) { $user->setVar('user_language', $data['user_language']); }
return $this->userHandler->insertUser($user); }
/** * Change user password * * @param int $uid User ID * @param string $currentPassword Current password * @param string $newPassword New password * @return bool Success status */ public function changePassword( int $uid, string $currentPassword, string $newPassword ): bool { $user = $this->userHandler->getUser($uid); if (!$user) { return false; }
// Verify current password if (!password_verify($currentPassword, $user->getVar('pass'))) { throw new Exception('Current password is incorrect'); }
// Validate new password if (strlen($newPassword) < 8) { throw new Exception('New password must be at least 8 characters'); }
// Hash new password $hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => 12]); $user->setVar('pass', $hashedPassword);
return $this->userHandler->insertUser($user); }
/** * Get user profile data * * @param int $uid User ID * @return array Profile data */ public function getProfile(int $uid): array { $user = $this->userHandler->getUser($uid); if (!$user) { return []; }
return [ 'uid' => $user->getVar('uid'), 'uname' => $user->getVar('uname'), 'email' => $user->getVar('email'), 'user_regdate' => $user->getVar('user_regdate'), 'user_icq' => $user->getVar('user_icq'), 'user_from' => $user->getVar('user_from'), 'user_sig' => $user->getVar('user_sig'), 'user_viewemail' => $user->getVar('user_viewemail'), 'user_attachsig' => $user->getVar('user_attachsig'), 'user_theme' => $user->getVar('user_theme'), 'user_language' => $user->getVar('user_language'), 'last_login' => $user->getVar('last_login'), 'avatar' => $user->getVar('user_avatar') ]; }}Avatar Handling
Section titled “Avatar Handling”Avatar Management
Section titled “Avatar Management”<?php/** * User Avatar Handler */class AvatarHandler{ private $avatarPath = '/uploads/avatars/'; private $maxSize = 2097152; // 2MB private $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
/** * Upload user avatar * * @param int $uid User ID * @param array $file $_FILES array * @return string|false Avatar filename or false */ public function uploadAvatar(int $uid, array $file) { // Validate file if ($file['error'] !== UPLOAD_ERR_OK) { throw new Exception('File upload error: ' . $file['error']); }
if ($file['size'] > $this->maxSize) { throw new Exception('File too large (max 2MB)'); }
if (!in_array($file['type'], $this->allowedTypes)) { throw new Exception('Invalid file type'); }
// Verify MIME type $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $file['tmp_name']); finfo_close($finfo);
if (!in_array($mimeType, $this->allowedTypes)) { throw new Exception('Invalid file content'); }
// Generate unique filename $extension = pathinfo($file['name'], PATHINFO_EXTENSION); $filename = 'avatar_' . $uid . '_' . time() . '.' . $extension;
// Create upload directory $uploadDir = XOOPS_ROOT_PATH . $this->avatarPath; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); }
$filepath = $uploadDir . $filename;
// Move uploaded file if (!move_uploaded_file($file['tmp_name'], $filepath)) { throw new Exception('Failed to move uploaded file'); }
// Resize image to standard size (150x150) $this->resizeImage($filepath, 150, 150);
// Update user avatar $userHandler = xoops_getHandler('user'); $user = $userHandler->getUser($uid);
// Delete old avatar if exists $oldAvatar = $user->getVar('user_avatar'); if ($oldAvatar && $oldAvatar !== 'blank.gif') { $oldPath = $uploadDir . $oldAvatar; if (file_exists($oldPath)) { unlink($oldPath); } }
// Save new avatar $user->setVar('user_avatar', $filename); $userHandler->insertUser($user);
return $filename; }
/** * Resize image to specified dimensions * * @param string $filepath Path to image file * @param int $width Target width * @param int $height Target height */ private function resizeImage(string $filepath, int $width, int $height): void { if (!extension_loaded('gd')) { return; // GD not available, skip resizing }
$image = imagecreatefromstring(file_get_contents($filepath)); if (!$image) { return; }
$resized = imagecreatetruecolor($width, $height);
// Preserve transparency for PNG and GIF $format = mime_content_type($filepath); if ($format === 'image/png' || $format === 'image/gif') { imagealphablending($resized, false); imagesavealpha($resized, true); }
imagecopyresampled( $resized, $image, 0, 0, 0, 0, $width, $height, imagesx($image), imagesy($image) );
// Save resized image $ext = pathinfo($filepath, PATHINFO_EXTENSION); if (strtolower($ext) === 'png') { imagepng($resized, $filepath, 9); } else { imagejpeg($resized, $filepath, 90); }
imagedestroy($image); imagedestroy($resized); }
/** * Delete user avatar * * @param int $uid User ID * @return bool Success status */ public function deleteAvatar(int $uid): bool { $userHandler = xoops_getHandler('user'); $user = $userHandler->getUser($uid);
if (!$user) { return false; }
$avatar = $user->getVar('user_avatar'); if ($avatar && $avatar !== 'blank.gif') { $filepath = XOOPS_ROOT_PATH . $this->avatarPath . $avatar; if (file_exists($filepath)) { unlink($filepath); } }
$user->setVar('user_avatar', 'blank.gif'); return $userHandler->insertUser($user); }}User Preferences
Section titled “User Preferences”Preference System
Section titled “Preference System”<?php/** * User Preferences Handler */class UserPreferencesHandler{ private $userHandler; private $prefixCache = 'user_pref_';
public function __construct() { $this->userHandler = xoops_getHandler('user'); }
/** * Get user preference * * @param int $uid User ID * @param string $prefKey Preference key * @param mixed $default Default value * @return mixed Preference value */ public function getPreference(int $uid, string $prefKey, $default = null) { // Try cache first $cacheKey = $this->prefixCache . $uid . '_' . $prefKey; $cached = apcu_fetch($cacheKey); if ($cached !== false) { return $cached; }
// Get from database $db = XoopsDatabaseFactory::getDatabaseConnection(); $result = $db->query( "SELECT pref_value FROM xoops_user_preferences WHERE uid = ? AND pref_key = ?", array($uid, $prefKey) );
if ($result && $db->getRowCount($result) > 0) { $row = $db->fetchArray($result); $value = unserialize($row['pref_value']); apcu_store($cacheKey, $value, 3600); // Cache for 1 hour return $value; }
return $default; }
/** * Set user preference * * @param int $uid User ID * @param string $prefKey Preference key * @param mixed $prefValue Preference value * @return bool Success status */ public function setPreference(int $uid, string $prefKey, $prefValue): bool { $db = XoopsDatabaseFactory::getDatabaseConnection();
// Check if preference exists $result = $db->query( "SELECT id FROM xoops_user_preferences WHERE uid = ? AND pref_key = ?", array($uid, $prefKey) );
$serialized = serialize($prefValue);
if ($db->getRowCount($result) > 0) { // Update existing preference $success = $db->query( "UPDATE xoops_user_preferences SET pref_value = ? WHERE uid = ? AND pref_key = ?", array($serialized, $uid, $prefKey) ); } else { // Insert new preference $success = $db->query( "INSERT INTO xoops_user_preferences (uid, pref_key, pref_value) VALUES (?, ?, ?)", array($uid, $prefKey, $serialized) ); }
if ($success) { // Clear cache $cacheKey = $this->prefixCache . $uid . '_' . $prefKey; apcu_delete($cacheKey); }
return (bool)$success; }
/** * Get all user preferences * * @param int $uid User ID * @return array All preferences */ public function getAllPreferences(int $uid): array { $db = XoopsDatabaseFactory::getDatabaseConnection(); $result = $db->query( "SELECT pref_key, pref_value FROM xoops_user_preferences WHERE uid = ?", array($uid) );
$prefs = []; while ($row = $db->fetchArray($result)) { $prefs[$row['pref_key']] = unserialize($row['pref_value']); }
return $prefs; }
/** * Delete user preference * * @param int $uid User ID * @param string $prefKey Preference key * @return bool Success status */ public function deletePreference(int $uid, string $prefKey): bool { $db = XoopsDatabaseFactory::getDatabaseConnection(); $success = $db->query( "DELETE FROM xoops_user_preferences WHERE uid = ? AND pref_key = ?", array($uid, $prefKey) );
if ($success) { $cacheKey = $this->prefixCache . $uid . '_' . $prefKey; apcu_delete($cacheKey); }
return (bool)$success; }}User Operations Examples
Section titled “User Operations Examples”Common User Operations
Section titled “Common User Operations”<?php/** * Common user operations examples */
// Get current logged-in user$xoopsUser = $GLOBALS['xoopsUser'];if ($xoopsUser instanceof XoopsUser) { $userId = $xoopsUser->getVar('uid'); $username = $xoopsUser->getVar('uname');}
// Get user by ID$userHandler = xoops_getHandler('user');$user = $userHandler->getUser(1);echo $user->getVar('uname');
// Get user by username$user = $userHandler->getUserByName('admin');if ($user) { echo $user->getVar('email');}
// Get user by email$user = $userHandler->getUserByEmail('user@example.com');
// Get all users in a group$users = $userHandler->getUsersByGroup(1);foreach ($users as $user) { echo $user->getVar('uname') . "\n";}
// Create new user$user = $userHandler->create();$user->setVar('uname', 'newuser');$user->setVar('email', 'newuser@example.com');$user->setVar('pass', password_hash('password', PASSWORD_BCRYPT));$user->setVar('user_regdate', time());
if ($userHandler->insertUser($user)) { echo "User created: " . $user->getVar('uid');}
// Delete user$userHandler->deleteUser(123);
// Get user object from ID$user = $userHandler->getUser(5);$profile = [ 'username' => $user->getVar('uname'), 'email' => $user->getVar('email'), 'regdate' => date('Y-m-d', $user->getVar('user_regdate')), 'avatar' => $user->getVar('user_avatar'),];Security Best Practices
Section titled “Security Best Practices”Password Security
Section titled “Password Security”- Always use
password_hash()withPASSWORD_BCRYPTalgorithm - Use cost parameter of 12 for bcrypt
- Never store plain text passwords
- Implement password expiration policies
- Require password changes for compromised accounts
Session Security
Section titled “Session Security”<?php// Session configurationsession_set_cookie_params([ 'lifetime' => 0, // Session cookie (deleted on browser close) 'path' => '/', 'domain' => '', 'secure' => true, // HTTPS only 'httponly' => true, // Inaccessible to JavaScript 'samesite' => 'Strict' // CSRF protection]);
session_start();
// Regenerate session ID after loginsession_regenerate_id(true);
// Validate session tokenif (!isset($_SESSION['xoopsSessionToken'])) { session_destroy(); redirect('login');}Related Links
Section titled “Related Links”- Group System.md
- Permission System.md
- Authentication.md
- ../../Security/Security-Guidelines.md
#users #registration #authentication #profiles #password-security #sessions