Files
ave-cms/system/App/Frontend/Auth/UserRepository.php
T
2026-07-27 12:58:44 +03:00

309 lines
11 KiB
PHP

<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file system/App/Frontend/Auth/UserRepository.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Frontend\Auth;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use DB;
use App\Common\DatabaseSchema;
use App\Content\PublicUserTables;
use App\Helpers\Phone;
class UserRepository
{
public function findByEmail($email)
{
if (!filter_var(trim((string) $email), FILTER_VALIDATE_EMAIL)) {
return null;
}
$row = DB::query('SELECT * FROM `' . PublicUserTables::table('users') . '` WHERE LOWER(email)=LOWER(%s) AND deleted!=%s LIMIT 1', trim((string) $email), '1')->getAssoc();
return $row ? (array) $row : null;
}
public function findByPhone($phone)
{
$phone = Phone::normalize($phone);
if ($phone === '') {
return null;
}
if (!DatabaseSchema::columnExists(PublicUserTables::table('users'), 'phone_normalized')) {
throw new \RuntimeException('Схема телефонной авторизации не обновлена. Примените миграции ядра.');
}
$row = DB::query(
'SELECT * FROM `' . PublicUserTables::table('users') . '`'
. ' WHERE phone_normalized=%s AND deleted!=%s LIMIT 1',
$phone,
'1'
)->getAssoc();
if ($row) {
return (array) $row;
}
return null;
}
public function find($id)
{
$row = DB::query('SELECT * FROM `' . PublicUserTables::table('users') . '` WHERE Id=%i AND deleted!=%s LIMIT 1', (int) $id, '1')->getAssoc();
return $row ? (array) $row : null;
}
public function create(array $data, $group)
{
$email = mb_strtolower(trim((string) $data['email']));
DB::Insert(PublicUserTables::table('users'), array_merge($this->recordDefaults(), array(
'email' => $email,
'password' => password_hash((string) $data['password'], PASSWORD_BCRYPT, array('cost' => 12)),
'firstname' => trim((string) $data['firstname']),
'lastname' => trim((string) $data['lastname']),
'phone' => trim((string) (isset($data['phone']) ? $data['phone'] : '')),
'company' => trim((string) (isset($data['company']) ? $data['company'] : '')),
'user_name' => $this->userName($email),
'user_group' => (int) $group,
'reg_time' => time(),
'status' => '0',
'reg_ip' => $this->ipLong(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''),
'email_verified_at' => 0,
)));
return $this->find((int) DB::insertId());
}
public function createSocial(array $profile, $group)
{
$email = mb_strtolower(trim((string) (isset($profile['email']) ? $profile['email'] : '')));
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Провайдер не передал подтверждённый email.');
}
$password = bin2hex(random_bytes(32));
$userName = mb_strlen($email) <= 50
? $email
: mb_substr($email, 0, 37) . '_' . substr(sha1($email), 0, 12);
DB::Insert(PublicUserTables::table('users'), array_merge($this->recordDefaults(), array(
'email' => $email,
'password' => password_hash($password, PASSWORD_BCRYPT, array('cost' => 12)),
'firstname' => mb_substr(trim((string) (isset($profile['firstname']) ? $profile['firstname'] : '')), 0, 50),
'lastname' => mb_substr(trim((string) (isset($profile['lastname']) ? $profile['lastname'] : '')), 0, 50),
'phone' => '', 'company' => '', 'user_name' => $userName,
'user_group' => (int) $group, 'reg_time' => time(), 'status' => '1',
'reg_ip' => $this->ipLong(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''),
'email_verified_at' => time(),
)));
return $this->find((int) DB::insertId());
}
public function createCheckout(array $data, $group)
{
$email = mb_strtolower(trim((string) $data['email']));
$password = bin2hex(random_bytes(32));
$userName = mb_strlen($email) <= 50
? $email
: mb_substr($email, 0, 37) . '_' . substr(sha1($email), 0, 12);
DB::Insert(PublicUserTables::table('users'), array_merge($this->recordDefaults(), array(
'email' => $email,
'password' => password_hash($password, PASSWORD_BCRYPT, array('cost' => 12)),
'firstname' => mb_substr(trim((string) (isset($data['firstname']) ? $data['firstname'] : '')), 0, 50),
'lastname' => mb_substr(trim((string) (isset($data['lastname']) ? $data['lastname'] : '')), 0, 50),
'phone' => mb_substr(trim((string) (isset($data['phone']) ? $data['phone'] : '')), 0, 50),
'company' => '',
'city' => mb_substr(trim((string) (isset($data['city']) ? $data['city'] : '')), 0, 100),
'street' => mb_substr(trim((string) (isset($data['street']) ? $data['street'] : '')), 0, 255),
'street_nr' => '',
'zipcode' => mb_substr(trim((string) (isset($data['zipcode']) ? $data['zipcode'] : '')), 0, 20),
'user_name' => $userName,
'user_group' => (int) $group,
'reg_time' => time(),
'status' => '1',
'reg_ip' => $this->ipLong(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''),
'email_verified_at' => 0,
)));
return $this->find((int) DB::insertId());
}
public function createPhone($phone, $group)
{
$phone = Phone::normalize($phone);
if ($phone === '') {
throw new \InvalidArgumentException('Укажите корректный номер телефона.');
}
if ($this->findByPhone($phone)) {
throw new \InvalidArgumentException('Этот номер телефона уже используется.');
}
$password = bin2hex(random_bytes(32));
$userName = $this->uniqueUserName($phone);
DB::Insert(PublicUserTables::table('users'), array_merge($this->recordDefaults(), array(
'email' => null,
'password' => password_hash($password, PASSWORD_BCRYPT, array('cost' => 12)),
'phone' => $phone,
'phone_normalized' => $phone,
'phone_verified_at' => time(),
'user_name' => $userName,
'user_group' => (int) $group,
'reg_time' => time(),
'status' => '1',
'reg_ip' => $this->ipLong(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''),
)));
return $this->find((int) DB::insertId());
}
public function verifyEmail($id)
{
DB::query('UPDATE `' . PublicUserTables::table('users') . '` SET status=%s,email_verified_at=%i WHERE Id=%i', '1', time(), (int) $id);
}
public function activate($id)
{
DB::query('UPDATE `' . PublicUserTables::table('users') . '` SET status=%s WHERE Id=%i', '1', (int) $id);
}
public function verifyPhone($id, $phone)
{
$phone = Phone::normalize($phone);
if ($phone === '') {
throw new \InvalidArgumentException('Некорректный номер телефона.');
}
DB::Update(PublicUserTables::table('users'), array(
'phone' => $phone,
'phone_normalized' => $phone,
'phone_verified_at' => time(),
), 'Id=%i', (int) $id);
}
public function setPassword($id, $password)
{
$hash = password_hash((string) $password, PASSWORD_BCRYPT, array('cost' => 12));
DB::query('UPDATE `' . PublicUserTables::table('users') . '` SET password=%s,salt=%s,new_pass=%s,new_salt=%s WHERE Id=%i', $hash, '', '', '', (int) $id);
}
public function verifyPassword(array $user, $password)
{
$hash = isset($user['password']) ? (string) $user['password'] : '';
if (strlen($hash) === 32) {
return hash_equals($hash, md5(md5((string) $password . (string) $user['salt'])));
}
return password_verify((string) $password, $hash);
}
public function updateProfile($id, array $data)
{
$current = $this->find((int) $id);
$phone = Phone::normalize(isset($data['phone']) ? $data['phone'] : '');
if (trim((string) (isset($data['phone']) ? $data['phone'] : '')) !== '' && $phone === '') {
throw new \InvalidArgumentException('Укажите корректный номер телефона.');
}
$duplicate = $phone !== '' ? $this->findByPhone($phone) : null;
if ($duplicate && (int) $duplicate['Id'] !== (int) $id) {
throw new \InvalidArgumentException('Этот номер телефона уже используется.');
}
$currentPhone = $current ? Phone::normalize(isset($current['phone']) ? $current['phone'] : '') : '';
if ($current && empty($current['email']) && !empty($current['phone_verified_at']) && $currentPhone !== $phone) {
throw new \InvalidArgumentException('Подтверждённый телефон нельзя изменить без повторной проверки.');
}
$phoneUnchanged = $current && $currentPhone === $phone;
DB::Update(PublicUserTables::table('users'), array(
'firstname' => trim((string) $data['firstname']),
'lastname' => trim((string) $data['lastname']),
'phone' => $phone,
'phone_normalized' => $phoneUnchanged && !empty($current['phone_normalized'])
? (string) $current['phone_normalized']
: null,
'phone_verified_at' => $phoneUnchanged ? (int) $current['phone_verified_at'] : 0,
'company' => trim((string) $data['company']),
'city' => trim((string) $data['city']),
'street' => trim((string) $data['street']),
'street_nr' => trim((string) $data['street_nr']),
'zipcode' => trim((string) $data['zipcode']),
), 'Id=%i', (int) $id);
}
protected function ipLong($ip)
{
$value = ip2long((string) $ip);
return $value === false ? 0 : sprintf('%u', $value);
}
protected function userName($email)
{
$email = trim((string) $email);
return mb_strlen($email) <= 50
? $email
: mb_substr($email, 0, 37) . '_' . substr(sha1($email), 0, 12);
}
protected function uniqueUserName($preferred)
{
$preferred = mb_substr(trim((string) $preferred), 0, 50);
$candidate = $preferred;
$index = 2;
while (DB::query(
'SELECT Id FROM `' . PublicUserTables::table('users') . '` WHERE user_name=%s LIMIT 1',
$candidate
)->getValue()) {
$suffix = '-' . $index++;
$candidate = mb_substr($preferred, 0, 50 - strlen($suffix)) . $suffix;
}
return $candidate;
}
protected function recordDefaults()
{
return array(
'password' => '',
'email' => null,
'email_verified_at' => 0,
'street' => '',
'street_nr' => '',
'zipcode' => '',
'city' => '',
'phone' => '',
'phone_normalized' => null,
'phone_verified_at' => 0,
'telefax' => '',
'description' => '',
'firstname' => '',
'lastname' => '',
'user_name' => '',
'user_group' => 4,
'user_group_extra' => '',
'reg_time' => time(),
'status' => '1',
'last_visit' => 0,
'country' => 'RU',
'birthday' => '',
'deleted' => '0',
'del_time' => 0,
'emc' => '',
'reg_ip' => '0',
'new_pass' => '',
'company' => '',
'taxpay' => '0',
'salt' => '',
'new_salt' => '',
'user_ip' => 0,
);
}
}