Files
cmsPRO/autoload/Domain/User/UserRepository.php
Jacek Pyziak 9ee4116f50 Refaktoryzacja Faza 0+1: PSR-4 autoloader + Shared/Domain klasy
- Dodano PSR-4 autoloader do wszystkich 6 punktów wejścia
- Shared\: CacheHandler, Helpers, Html, ImageManipulator, Tpl
- Domain\: LanguagesRepository, SettingsRepository, UserRepository
- Stare class.*.php → cienkie wrappery (kompatybilność wsteczna)
- Dodano dokumentację: docs/PROJECT_STRUCTURE.md + pozostałe docs/
- Dodano CLAUDE.md z workflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 23:43:55 +01:00

236 lines
7.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Domain\User;
class UserRepository
{
private $db;
public function __construct( $db )
{
$this->db = $db;
}
// -------------------------------------------------------------------------
// Odczyt
// -------------------------------------------------------------------------
public function find( int $userId ): ?array
{
return $this->db->get( 'pp_users', '*', [ 'id' => $userId ] ) ?: null;
}
public function findByLogin( string $login ): ?array
{
return $this->db->get( 'pp_users', '*', [ 'login' => $login ] ) ?: null;
}
public function all(): array
{
return $this->db->select( 'pp_users', '*' ) ?: [];
}
public function privileges( int $userId ): array
{
return $this->db->select( 'pp_users_privileges', '*', [ 'id_user' => $userId ] ) ?: [];
}
public function hasPrivilege( string $name, int $userId ): bool
{
if ( $userId === 1 )
return true;
if ( !$result = \Shared\Cache\CacheHandler::fetch( "check_privileges:$userId:$name-tmp" ) )
{
$result = $this->db->count( 'pp_users_privileges', [ 'AND' => [ 'name' => $name, 'id_user' => $userId ] ] );
\Shared\Cache\CacheHandler::store( "check_privileges:$userId:$name", $result );
}
return (bool) $result;
}
// -------------------------------------------------------------------------
// Logowanie
// -------------------------------------------------------------------------
/**
* Weryfikuje login i hasło.
* @return int 1 = OK, 0 = złe dane, -1 = konto zablokowane
*/
public function logon( string $login, string $password ): int
{
if ( !$this->db->get( 'pp_users', '*', [ 'login' => $login ] ) )
return 0;
if ( !$this->db->get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'error_logged_count[<]' => 5 ] ] ) )
return -1;
if ( $this->db->get( 'pp_users', '*', [
'AND' => [
'login' => $login,
'status' => 1,
'password' => md5( $password ),
'OR' => [ 'active_to[>=]' => date( 'Y-m-d' ), 'active_to' => null ]
]
] ) ) {
$this->db->update( 'pp_users', [ 'last_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count' => 0 ], [ 'login' => $login ] );
return 1;
}
$this->db->update( 'pp_users', [ 'last_error_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count[+]' => 1 ], [ 'login' => $login ] );
if ( $this->db->get( 'pp_users', 'error_logged_count', [ 'login' => $login ] ) >= 5 )
{
$this->db->update( 'pp_users', [ 'status' => 0 ], [ 'login' => $login ] );
return -1;
}
return 0;
}
public function isLoginTaken( string $login, int $excludeId = 0 ): bool
{
return (bool) $this->db->get( 'pp_users', 'login', [ 'AND' => [ 'login' => $login, 'id[!]' => $excludeId ] ] );
}
// -------------------------------------------------------------------------
// 2FA
// -------------------------------------------------------------------------
public function update( int $userId, array $data ): bool
{
return (bool) $this->db->update( 'pp_users', $data, [ 'id' => $userId ] );
}
public function sendTwofaCode( int $userId, bool $resend = false ): bool
{
$user = $this->find( $userId );
if ( !$user ) return false;
if ( (int)$user['twofa_enabled'] !== 1 ) return false;
$to = $user['twofa_email'] ?: $user['login'];
if ( !filter_var( $to, FILTER_VALIDATE_EMAIL ) ) return false;
if ( $resend && !empty( $user['twofa_sent_at'] ) )
{
$last = strtotime( $user['twofa_sent_at'] );
if ( $last && ( time() - $last ) < 30 ) return false;
}
$code = random_int( 100000, 999999 );
$hash = password_hash( (string)$code, PASSWORD_DEFAULT );
$this->update( $userId, [
'twofa_code_hash' => $hash,
'twofa_expires_at' => date( 'Y-m-d H:i:s', time() + 10 * 60 ),
'twofa_sent_at' => date( 'Y-m-d H:i:s' ),
'twofa_failed_attempts' => 0,
] );
$subject = 'Twój kod logowania 2FA';
$body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie zignoruj tę wiadomość i poinformuj administratora.";
$sent = \S::send_email( $to, $subject, $body );
if ( !$sent )
{
$headers = "MIME-Version: 1.0\r\n";
$headers .= "Content-type: text/plain; charset=UTF-8\r\n";
$headers .= "From: no-reply@" . ( $_SERVER['HTTP_HOST'] ?? 'localhost' ) . "\r\n";
$sent = mail( $to, mb_encode_mimeheader( $subject, 'UTF-8' ), $body, $headers );
}
return (bool) $sent;
}
public function verifyTwofaCode( int $userId, string $code ): bool
{
$user = $this->find( $userId );
if ( !$user ) return false;
if ( (int)$user['twofa_failed_attempts'] >= 5 ) return false;
if ( empty( $user['twofa_expires_at'] ) || time() > strtotime( $user['twofa_expires_at'] ) )
{
$this->update( $userId, [ 'twofa_code_hash' => null, 'twofa_expires_at' => null ] );
return false;
}
$ok = !empty( $user['twofa_code_hash'] ) && password_verify( $code, $user['twofa_code_hash'] );
if ( $ok )
{
$this->update( $userId, [
'twofa_code_hash' => null,
'twofa_expires_at' => null,
'twofa_sent_at' => null,
'twofa_failed_attempts' => 0,
'last_logged' => date( 'Y-m-d H:i:s' ),
] );
return true;
}
$this->update( $userId, [
'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1,
'last_error_logged' => date( 'Y-m-d H:i:s' ),
] );
return false;
}
// -------------------------------------------------------------------------
// Zapis / usuwanie
// -------------------------------------------------------------------------
public function save(
$userId, string $login, $status, $activeTo, string $password, string $passwordRe,
$admin, $privileges, $twofaEnabled = 0, string $twofaEmail = ''
): array {
$this->db->delete( 'pp_users_privileges', [ 'id_user' => (int)$userId ] );
if ( !$userId )
{
if ( strlen( $password ) < 5 )
return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
if ( $password !== $passwordRe )
return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ];
$this->db->insert( 'pp_users', [
'login' => $login,
'status' => $status == 'on' ? 1 : 0,
'active_to' => $activeTo === '' ? null : $activeTo,
'admin' => $admin,
'password' => md5( $password ),
'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0,
'twofa_email' => $twofaEmail,
] );
$userId = $this->db->get( 'pp_users', 'id', [ 'ORDER' => [ 'id' => 'DESC' ] ] );
}
else
{
if ( $password && strlen( $password ) < 5 )
return [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
if ( $password && $password !== $passwordRe )
return [ 'status' => 'error', 'msg' => 'Podane hasła są różne.' ];
if ( $password )
$this->db->update( 'pp_users', [ 'password' => md5( $password ) ], [ 'id' => (int)$userId ] );
$this->db->update( 'pp_users', [
'login' => $login,
'admin' => $admin,
'status' => $status == 'on' ? 1 : 0,
'active_to' => $activeTo === '' ? null : $activeTo,
'error_logged_count' => 0,
'twofa_enabled' => $twofaEnabled == 'on' ? 1 : 0,
'twofa_email' => $twofaEmail,
], [ 'id' => (int)$userId ] );
}
$privileges = (array)$privileges;
foreach ( $privileges as $pri )
$this->db->insert( 'pp_users_privileges', [ 'name' => $pri, 'id_user' => $userId ] );
\S::delete_cache();
return [ 'status' => 'ok', 'msg' => 'Użytkownik został zapisany.' ];
}
public function delete( int $userId ): bool
{
return (bool) $this->db->delete( 'pp_users', [ 'id' => $userId ] );
}
}