Files
domnitor/app/Models/User.php
Hosteroid 0d4a38aae8 Refactor admin/user isolation logic and model methods
Moved admin/user isolation checks and related methods from Domain and NotificationGroup models to User model for better separation of concerns. Replaced direct database queries in controllers and services with new model methods. Added methods for assigning unassigned domains/groups, searching domains, and clearing old notification logs. Updated views for improved UI consistency.
2025-10-20 17:25:02 +03:00

342 lines
10 KiB
PHP

<?php
namespace App\Models;
use Core\Model;
class User extends Model
{
protected static string $table = 'users';
/**
* Find user by username
*/
public function findByUsername(string $username): ?array
{
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ? AND is_active = 1");
$stmt->execute([$username]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Verify password
*/
public function verifyPassword(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
/**
* Update last login timestamp
*/
public function updateLastLogin(int $userId): bool
{
$stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?");
return $stmt->execute([$userId]);
}
/**
* Create user with hashed password
*/
public function createUser(string $username, string $password, ?string $email = null, ?string $fullName = null): int
{
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
return $this->create([
'username' => $username,
'password' => $hashedPassword,
'email' => $email,
'full_name' => $fullName,
'is_active' => 1
]);
}
/**
* Change password
*/
public function changePassword(int $userId, string $newPassword): bool
{
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $this->db->prepare("UPDATE users SET password = ? WHERE id = ?");
return $stmt->execute([$hashedPassword, $userId]);
}
/**
* Get users with filters, sorting, and pagination
*/
public function getFiltered(array $filters = [], string $sort = 'username', string $order = 'ASC', int $limit = 25, int $offset = 0): array
{
$query = "SELECT * FROM users WHERE 1=1";
$params = [];
// Apply search filter
if (!empty($filters['search'])) {
$query .= " AND (username LIKE ? OR email LIKE ? OR full_name LIKE ?)";
$searchTerm = "%{$filters['search']}%";
$params[] = $searchTerm;
$params[] = $searchTerm;
$params[] = $searchTerm;
}
// Apply role filter
if (!empty($filters['role'])) {
$query .= " AND role = ?";
$params[] = $filters['role'];
}
// Apply status filter
if (isset($filters['status']) && $filters['status'] !== '') {
$isActive = ($filters['status'] === 'active') ? 1 : 0;
$query .= " AND is_active = ?";
$params[] = $isActive;
}
// Apply sorting
$allowedSortColumns = ['username', 'email', 'full_name', 'role', 'is_active', 'email_verified', 'last_login', 'created_at'];
if (!in_array($sort, $allowedSortColumns)) {
$sort = 'username';
}
$order = strtoupper($order) === 'DESC' ? 'DESC' : 'ASC';
$query .= " ORDER BY {$sort} {$order}";
// Apply pagination
$query .= " LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
$stmt = $this->db->prepare($query);
$stmt->execute($params);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Count users with filters
*/
public function countFiltered(array $filters = []): int
{
$query = "SELECT COUNT(*) as total FROM users WHERE 1=1";
$params = [];
// Apply search filter
if (!empty($filters['search'])) {
$query .= " AND (username LIKE ? OR email LIKE ? OR full_name LIKE ?)";
$searchTerm = "%{$filters['search']}%";
$params[] = $searchTerm;
$params[] = $searchTerm;
$params[] = $searchTerm;
}
// Apply role filter
if (!empty($filters['role'])) {
$query .= " AND role = ?";
$params[] = $filters['role'];
}
// Apply status filter
if (isset($filters['status']) && $filters['status'] !== '') {
$isActive = ($filters['status'] === 'active') ? 1 : 0;
$query .= " AND is_active = ?";
$params[] = $isActive;
}
$stmt = $this->db->prepare($query);
$stmt->execute($params);
return (int)$stmt->fetch(\PDO::FETCH_ASSOC)['total'];
}
/**
* Update email verification token
*/
public function updateEmailVerificationToken(int $userId, string $token): bool
{
$stmt = $this->db->prepare(
"UPDATE users SET email_verification_token = ?, email_verification_sent_at = NOW() WHERE id = ?"
);
return $stmt->execute([$token, $userId]);
}
/**
* Mark email as verified
*/
public function markEmailAsVerified(int $userId): bool
{
$stmt = $this->db->prepare("UPDATE users SET email_verified = 1 WHERE id = ?");
return $stmt->execute([$userId]);
}
/**
* Verify email by clearing token
*/
public function verifyEmailByToken(int $userId): bool
{
$stmt = $this->db->prepare(
"UPDATE users SET email_verified = 1, email_verification_token = NULL WHERE id = ?"
);
return $stmt->execute([$userId]);
}
/**
* Find user by email verification token
*/
public function findByVerificationToken(string $token): ?array
{
$stmt = $this->db->prepare(
"SELECT * FROM users WHERE email_verification_token = ? AND (email_verified IS NULL OR email_verified = 0)"
);
$stmt->execute([$token]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Create password reset token
*/
public function createPasswordResetToken(int $userId, string $token, string $expiresAt): bool
{
$stmt = $this->db->prepare(
"INSERT INTO password_reset_tokens (user_id, token, expires_at) VALUES (?, ?, ?)"
);
return $stmt->execute([$userId, $token, $expiresAt]);
}
/**
* Find valid password reset token
*/
public function findPasswordResetToken(string $token): ?array
{
$stmt = $this->db->prepare(
"SELECT * FROM password_reset_tokens WHERE token = ? AND used = 0 AND expires_at > NOW()"
);
$stmt->execute([$token]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Mark password reset token as used
*/
public function markPasswordResetTokenAsUsed(int $tokenId): bool
{
$stmt = $this->db->prepare("UPDATE password_reset_tokens SET used = 1 WHERE id = ?");
return $stmt->execute([$tokenId]);
}
/**
* Create remember token
*/
public function createRememberToken(int $userId, string $sessionId, string $token, string $expiresAt): bool
{
$stmt = $this->db->prepare(
"INSERT INTO remember_tokens (user_id, session_id, token, expires_at) VALUES (?, ?, ?, ?)"
);
return $stmt->execute([$userId, $sessionId, $token, $expiresAt]);
}
/**
* Find user by remember token
*/
public function findByRememberToken(string $token): ?array
{
$stmt = $this->db->prepare(
"SELECT user_id FROM remember_tokens WHERE token = ? AND expires_at > NOW()"
);
$stmt->execute([$token]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Delete remember token
*/
public function deleteRememberToken(string $token): bool
{
$stmt = $this->db->prepare("DELETE FROM remember_tokens WHERE token = ?");
return $stmt->execute([$token]);
}
/**
* Get user's 2FA status
*/
public function getTwoFactorStatus(int $userId): array
{
$user = $this->find($userId);
if (!$user) {
return ['enabled' => false, 'can_enable' => false, 'required' => false];
}
$twoFactorService = new \App\Services\TwoFactorService();
return [
'enabled' => (bool)$user['two_factor_enabled'],
'can_enable' => $twoFactorService->canEnableTwoFactor($userId),
'required' => $twoFactorService->isTwoFactorRequired($userId),
'setup_at' => $user['two_factor_setup_at'],
'backup_codes_count' => $user['two_factor_backup_codes'] ? count(json_decode($user['two_factor_backup_codes'], true)) : 0
];
}
/**
* Check if user has verified email (required for 2FA)
*/
public function hasVerifiedEmail(int $userId): bool
{
$user = $this->find($userId);
return $user && $user['email_verified'];
}
/**
* Check if user is admin
*/
public function isAdmin(?int $userId): bool
{
if (!$userId) {
return false;
}
$stmt = $this->db->prepare("SELECT role FROM users WHERE id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch();
return $user && $user['role'] === 'admin';
}
/**
* Get first admin user
*/
public function getFirstAdminUser(): ?array
{
$stmt = $this->db->query("SELECT * FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1");
return $stmt->fetch() ?: null;
}
/**
* Get all admin users
*/
public function getAllAdmins(): array
{
return $this->where('role', 'admin');
}
/**
* Count admin users
*/
public function countAdmins(): int
{
$stmt = $this->db->query("SELECT COUNT(*) as count FROM users WHERE role = 'admin'");
$result = $stmt->fetch();
return (int)$result['count'];
}
/**
* Find user by verification token (debug version - includes all users regardless of verification status)
*/
public function findByVerificationTokenDebug(string $token): ?array
{
$stmt = $this->db->prepare("SELECT id, email, email_verified, email_verification_token FROM users WHERE email_verification_token = ?");
$stmt->execute([$token]);
$result = $stmt->fetch();
return $result ?: null;
}
}