Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
AccountMapper
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 2
240
0.00% covered (danger)
0.00%
0 / 1
 getWithPermissions
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 login
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
182
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   Modules\Admin\Models
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace Modules\Admin\Models;
16
17use phpOMS\Account\AccountStatus;
18use phpOMS\Auth\LoginReturnType;
19use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
20use phpOMS\DataStorage\Database\Query\Builder;
21
22/**
23 * Account mapper class.
24 *
25 * @package Modules\Admin\Models
26 * @license OMS License 2.0
27 * @link    https://jingga.app
28 * @since   1.0.0
29 *
30 * @template T of Account
31 * @extends DataMapperFactory<T>
32 */
33class AccountMapper extends DataMapperFactory
34{
35    /**
36     * Columns.
37     *
38     * @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
39     * @since 1.0.0
40     */
41    public const COLUMNS = [
42        'account_id'                  => ['name' => 'account_id',           'type' => 'int',      'internal' => 'id'],
43        'account_status'              => ['name' => 'account_status',       'type' => 'int',      'internal' => 'status'],
44        'account_type'                => ['name' => 'account_type',         'type' => 'int',      'internal' => 'type'],
45        'account_login'               => ['name' => 'account_login',        'type' => 'string',   'internal' => 'login', 'autocomplete' => true],
46        'account_name1'               => ['name' => 'account_name1',        'type' => 'string',   'internal' => 'name1', 'autocomplete' => true, 'annotations' => ['gdpr' => true]],
47        'account_name2'               => ['name' => 'account_name2',        'type' => 'string',   'internal' => 'name2', 'autocomplete' => true, 'annotations' => ['gdpr' => true]],
48        'account_name3'               => ['name' => 'account_name3',        'type' => 'string',   'internal' => 'name3', 'autocomplete' => true, 'annotations' => ['gdpr' => true]],
49        'account_email'               => ['name' => 'account_email',        'type' => 'string',   'internal' => 'email', 'autocomplete' => true, 'annotations' => ['gdpr' => true]],
50        'account_tries'               => ['name' => 'account_tries',        'type' => 'int',      'internal' => 'tries'],
51        'account_lactive'             => ['name' => 'account_lactive',      'type' => 'DateTime', 'internal' => 'lastActive'],
52        'account_localization'        => ['name' => 'account_localization', 'type' => 'int',      'internal' => 'l11n'],
53        'account_created_at'          => ['name' => 'account_created_at',   'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
54    ];
55
56    /**
57     * Has one relation.
58     *
59     * @var array<string, array{mapper:class-string, external:string, by?:string, column?:string, conditional?:bool}>
60     * @since 1.0.0
61     */
62    public const OWNS_ONE = [
63        'l11n'  => [
64            'mapper'     => LocalizationMapper::class,
65            'external'   => 'account_localization',
66        ],
67    ];
68
69    /**
70     * Has many relation.
71     *
72     * @var array<string, array{mapper:class-string, table:string, self?:?string, external?:?string, column?:string}>
73     * @since 1.0.0
74     */
75    public const HAS_MANY = [
76        'permissions' => [
77            'mapper'   => AccountPermissionMapper::class,
78            'table'    => 'account_permission',
79            'external' => null,
80            'self'     => 'account_permission_account',
81        ],
82        'groups' => [
83            'mapper'   => GroupMapper::class,
84            'table'    => 'account_group',
85            'external' => 'account_group_group',
86            'self'     => 'account_group_account',
87        ],
88        'parents' => [
89            'mapper'   => self::class,
90            'table'    => 'account_account_rel',
91            'external' => 'account_account_rel_root',
92            'self'     => 'account_account_rel_child',
93        ],
94        'locations' => [
95            'mapper'   => AddressMapper::class,
96            'table'    => 'account_address_rel',
97            'external' => 'account_address_rel_address',
98            'self'     => 'account_address_rel_account',
99        ],
100        'contacts' => [
101            'mapper'   => ContactMapper::class,
102            'table'    => 'account_contact',
103            'self'     => 'account_contact_account',
104            'external' => null,
105        ],
106    ];
107
108    /**
109     * Model to use by the mapper.
110     *
111     * @var class-string<T>
112     * @since 1.0.0
113     */
114    public const MODEL = Account::class;
115
116    /**
117     * Primary table.
118     *
119     * @var string
120     * @since 1.0.0
121     */
122    public const TABLE = 'account';
123
124    /**
125     * Primary field name.
126     *
127     * @var string
128     * @since 1.0.0
129     */
130    public const PRIMARYFIELD = 'account_id';
131
132    /**
133     * Created at column
134     *
135     * @var string
136     * @since 1.0.0
137     */
138    public const CREATED_AT = 'account_created_at';
139
140    /**
141     * Get account with permissions
142     *
143     * @param int $id Account id
144     *
145     * @return Account
146     *
147     * @since 1.0.0
148     */
149    public static function getWithPermissions(int $id) : Account
150    {
151        if ($id < 1) {
152            return new NullAccount();
153        }
154
155        /** @var \Modules\Admin\Models\Account $account */
156        $account = self::get()
157            ->with('groups')
158            ->with('groups/permissions')
159            ->with('permissions')
160            ->with('l11n')
161            ->where('id', $id)
162            ->where('permissions/element', null)
163            ->where('groups/permissions/element', null)
164            ->execute();
165
166        return $account;
167    }
168
169    /**
170     * Login user.
171     *
172     * @param string $login    Username
173     * @param string $password Password
174     * @param int    $tries    Allowed login tries
175     *
176     * @return int Login code
177     *
178     * @since 1.0.0
179     */
180    public static function login(string $login, string $password, int $tries = 3) : int
181    {
182        if (empty($password)) {
183            return LoginReturnType::WRONG_PASSWORD;
184        }
185
186        try {
187            $result = null;
188
189            $query  = new Builder(self::$db);
190            $result = $query->select('account_id', 'account_login', 'account_password', 'account_password_temp', 'account_tries', 'account_status')
191                ->from('account')
192                ->where('account_login', '=', $login)
193                ->execute()
194                ?->fetchAll();
195
196            if ($result === null || !isset($result[0])) {
197                return LoginReturnType::WRONG_USERNAME;
198            }
199
200            $result = $result[0];
201
202            if ($result['account_tries'] >= $tries) {
203                return LoginReturnType::WRONG_INPUT_EXCEEDED;
204            }
205
206            if ($result['account_status'] !== AccountStatus::ACTIVE) {
207                return LoginReturnType::INACTIVE;
208            }
209
210            if (empty($result['account_password'])) {
211                return LoginReturnType::EMPTY_PASSWORD;
212            }
213
214            if (\password_verify($password, $result['account_password'] ?? '')) {
215                $query->update('account')
216                    ->set([
217                        'account_lactive' => new \DateTime('now'),
218                        'account_tries'   => 0,
219                    ])
220                    ->where('account_id', '=', (int) $result['account_id'])
221                    ->execute();
222
223                return $result['account_id'];
224            }
225
226            if (!empty($result['account_password_temp'])
227                && $result['account_password_temp_limit'] !== null
228                && (new \DateTime('now'))->getTimestamp() < (new \DateTime($result['account_password_temp_limit']))->getTimestamp()
229                && \password_verify($password, $result['account_password_temp'] ?? '')
230            ) {
231                $query->update('account')
232                    ->set([
233                        'account_password_temp' => '',
234                        'account_lactive'       => new \DateTime('now'),
235                        'account_tries'         => 0,
236                    ])
237                    ->where('account_id', '=', (int) $result['account_id'])
238                    ->execute();
239
240                return $result['account_id'];
241            }
242
243            $query->update('account')
244                ->set([
245                    'account_tries' => $result['account_tries'] + 1,
246                ])
247                ->where('account_id', '=', (int) $result['account_id'])
248                ->execute();
249
250            return LoginReturnType::WRONG_PASSWORD;
251        } catch (\Exception $_) {
252            return LoginReturnType::FAILURE; // @codeCoverageIgnore
253        }
254    }
255}