Gestion des utilisateurs
User Management in XOOPS
Section intitulée « 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 intitulée « User Object Structure »The core user object in XOOPS is the XoopsUser class, which encapsulates all user data and methods.
Database Schema
Section intitulée « 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 intitulée « 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 intitulée « User Registration Flow »Registration Sequence Diagram
Section intitulée « 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 intitulée « 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 intitulée « User Authentication Process »Authentication Flow Diagram
Section intitulée « 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 intitulée « 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 intitulée « Profile Management »Profile Update Implementation
Section intitulée « 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 intitulée « Avatar Handling »Avatar Management
Section intitulée « 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 intitulée « User Preferences »Preference System
Section intitulée « 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 intitulée « User Operations Examples »Common User Operations
Section intitulée « 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 intitulée « Security Best Practices »Password Security
Section intitulée « 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 intitulée « 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 intitulée « Related Links »- Group System.md
- Permission System.md
- Authentication.md
- ../../Security/Security-Guidelines.md
#users #registration #authentication #profiles #password-security #sessions