Passed
Push — main ( ce87af...9fb0de )
by Nobufumi
02:38
created

UserModel::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Jidaikobo\Kontiki\Models;
4
5
use Jidaikobo\Kontiki\Core\Database;
6
use Jidaikobo\Kontiki\Validation\UserValidator;
7
8
class UserModel extends BaseModel
9
{
10
    use Traits\CRUDTrait;
11
    use Traits\MetaDataTrait;
0 ignored issues
show
introduced by
The trait Jidaikobo\Kontiki\Models\Traits\MetaDataTrait requires some properties which are not provided by Jidaikobo\Kontiki\Models\UserModel: $meta_key, $meta_value
Loading history...
12
    use Traits\IndexTrait;
13
14
    protected string $table = 'users';
15
16
    public function __construct(
17
        Database $db,
18
        UserValidator $validator
19
    ) {
20
        parent::__construct($db);
21
        $this->setValidator($validator);
22
    }
23
24
    protected function defineFieldDefinitions(): void
25
    {
26
        // add dynamic rules at $this->processFieldDefinitions()
27
        $this->fieldDefinitions = [
28
            'id' => $this->getIdField(),
29
30
            'username' => $this->getField(
31
                __('username'),
32
                [
33
                    'rules' => [
34
                        'required',
35
                        ['lengthMin', 3]
36
                    ],
37
                    'display_in_list' => true
38
                ]
39
            ),
40
41
            'password' => $this->getField(
42
                __('password'),
43
                [
44
                    'type' => 'password',
45
                    'rules' => [
46
                        'required',
47
                        ['lengthMin', 8]
48
                    ],
49
                    'filter' => FILTER_UNSAFE_RAW,
50
                ]
51
            ),
52
53
            'role' => $this->getField(
54
                __('role'),
55
                [
56
                    'type' => 'select',
57
                    'options' => [
58
                        'editor' => __('editor'),
59
                        'admin' => __('admin'),
60
                    ],
61
                    'rules' => [
62
                        'required',
63
                    ],
64
                    'attributes' => [
65
                        'class' => 'form-control form-select'
66
                    ],
67
                    'display_in_list' => true
68
                ]
69
            ),
70
71
            'created_at' => $this->getReadOnlyField(
72
                __('created_at', 'Created'),
73
                [
74
                    'display_in_list' => true
75
                ]
76
            ),
77
        ];
78
    }
79
80
    protected function processFieldDefinitions(
81
        string $context = '',
82
        array $data = [],
83
        int $id = null
84
    ): void {
85
        // add rule
86
        $this->fieldDefinitions['username']['rules'][] = [
87
            'unique',
88
            $this->table,
89
            'username',
90
            $id
91
        ];
92
93
        if ($context == 'create') {
94
            return;
95
        }
96
97
        // Exclude `required` from password's rules
98
        // No password specified means no change
99
        $this->fieldDefinitions['password']['rules'] = array_filter(
100
            $this->fieldDefinitions['password']['rules'],
101
            fn($rule) => $rule !== 'required'
102
        );
103
104
        $this->fieldDefinitions['password']['description'] = __('users_edit_message');
105
106
        // disable form elements
107
        if (in_array($context, ['trash', 'restore', 'delete'])) {
108
            $this->disableFormFieldsForContext();
109
        }
110
    }
111
112
    /**
113
     * Override the validation method to ensure that at least one "admin" remains in the system.
114
     *
115
     * @param array $data The data to validate.
116
     * @param array $fieldDefinitions The field definitions used for validation.
117
     * @return array An array containing 'valid' (boolean) and 'errors'.
118
     */
119
    public function validateByFields(array $data, array $fieldDefinitions, ?int $id = NULL): array
120
    {
121
        // Execute the parent validation logic
122
        $result = parent::validateByFields($data, $fieldDefinitions, $id);
0 ignored issues
show
Bug introduced by
The method validateByFields() does not exist on Jidaikobo\Kontiki\Models\BaseModel. It seems like you code against a sub-type of Jidaikobo\Kontiki\Models\BaseModel such as Jidaikobo\Kontiki\Models\UserModel. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

122
        /** @scrutinizer ignore-call */ 
123
        $result = parent::validateByFields($data, $fieldDefinitions, $id);
Loading history...
123
124
        // Check if the "role" field is being modified
125
        if (isset($data['role'])) {
126
            // Retrieve the target user's data from the database
127
            $targetUser = $this->getById($id ?? 0);
128
129
            // If the user is an "admin" and is attempting to change their role
130
            if ($targetUser && $targetUser['role'] === 'admin' && $data['role'] !== 'admin') {
131
                // Count the number of other admins in the system
132
                $adminCount = $this->db->table($this->table)
133
                    ->where('role', 'admin')
134
                    ->where('id', '!=', $targetUser['id']) // Exclude the current user
135
                    ->count();
136
137
                // If no other admins remain, return a validation error
138
                if ($adminCount === 0) {
139
                    $result['valid'] = false;
140
                    $result['errors']['role']['messages'] = [__('at_least_one_admin')];
141
                }
142
            }
143
        }
144
145
        return $result;
146
    }
147
148
    private function hashPassword(string $password): string
149
    {
150
        return password_hash($password, PASSWORD_BCRYPT);
0 ignored issues
show
Bug Best Practice introduced by
The expression return password_hash($pa...Models\PASSWORD_BCRYPT) could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
151
    }
152
153
    protected function processDataForForm(string $actionType, array $data): array
154
    {
155
        if ($actionType == 'edit') {
156
            $data['password'] = '';
157
        }
158
        return $data;
159
    }
160
161
    protected function afterProcessDataBeforeSave(string $context, array $data): array
162
    {
163
        if ($context == 'create') {
164
            $data['password'] = $this->hashPassword($data['password']);
165
        }
166
167
        if ($context == 'update') {
168
            // Branching password processing
169
            if (isset($data['password'])) {
170
                if (trim($data['password']) === '') {
171
                    unset($data['password']);
172
                } else {
173
                    $data['password'] = $this->hashPassword($data['password']);
174
                }
175
            }
176
        }
177
        return $data;
178
    }
179
}
180