Passed
Push — master ( 90dc34...40f09e )
by Christopher
01:53
created

src/ActiveRecord.php (3 issues)

1
<?php
2
/**
3
 * @link      https://github.com/chrmorandi/yii2-ldap for the source repository
4
 * @package   yii2-ldap
5
 * @author    Christopher Mota <[email protected]>
6
 * @license   MIT License - view the LICENSE file that was distributed with this source code.
7
 */
8
9
namespace chrmorandi\ldap;
10
11
use chrmorandi\ldap\ActiveQuery;
12
use chrmorandi\ldap\Connection;
13
use Yii;
14
use yii\base\InvalidConfigException;
15
use yii\db\BaseActiveRecord;
16
17
/**
18
 * ActiveRecord is the base class for classes representing relational data in terms of objects.
19
 *
20
 * This class implements the ActiveRecord pattern for the [ldap] protocol.
21
 *
22
 * For defining a record a subclass should at least implement the [[attributes()]] method to define
23
 * attributes or use some prepared traits for specific objects. A primary key can be defined via [[primaryKey()]] which defaults to `cn` if not specified.
24
 *
25
 * The following is an example model called `User`:
26
 *
27
 * ```php
28
 * class User extends \chrmorandi\ldap\ActiveRecord
29
 * {
30
 *     public function attributes()
31
 *     {
32
 *         return ['objectClass', 'cn', 'name'];
33
 *     }
34
 * }
35
 * ```
36
 * Or
37
 *
38
 * ```php
39
 * public function attributes() {
40
 *     return \chrmorandi\ldap\schemas\ADUser::getAttributes();
41
 * }
42
 * ```
43
 *
44
 * @since 1.0.0
45
 */
46
class ActiveRecord extends BaseActiveRecord
47
{
48
    /**
49
     * Returns the LDAP connection used by this AR class.
50
     *
51
     * @return Connection the LDAP connection used by this AR class.
52
     */
53
    public static function getDb()
54
    {
55
        return Yii::$app->get('ldap');
56
    }
57
58
    /**
59
     * @inheritdoc
60
     * @return ActiveQuery the newly created [[ActiveQuery]] instance.
61
     */
62
    public static function find()
63
    {
64
        return Yii::createObject(ActiveQuery::class, [get_called_class()]);
65
    }
66
67
    /**
68
     * Returns the primary key name(s) for this AR class.
69
     * This method should be overridden by child classes to define the primary key.
70
     *
71
     * Note that an array should be returned.
72
     *
73
     * @return string[] the primary keys of this record.
74
     */
75
    public static function primaryKey()
76
    {
77
        return ['dn'];
78
    }
79
80
    /**
81
     * Returns the list of attribute names.
82
     * You must override this method to define avaliable attributes.
83
     * @return array list of attribute names.
84
     */
85
    public function attributes()
86
    {
87
        throw new InvalidConfigException('The attributes() method of ldap ActiveRecord has to be implemented by child classes.');
88
    }
89
90
    /**
91
     * Inserts a record in the ldap using the attribute values.
92
     *
93
     * This method performs the following steps in order:
94
     *
95
     * 1. call [[beforeValidate()]] when `$runValidation` is true. If [[beforeValidate()]]
96
     *    returns `false`, the rest of the steps will be skipped;
97
     * 2. call [[afterValidate()]] when `$runValidation` is true. If validation
98
     *    failed, the rest of the steps will be skipped;
99
     * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`,
100
     *    the rest of the steps will be skipped;
101
     * 4. insert the record into LDAP. If this fails, it will skip the rest of the steps;
102
     * 5. call [[afterSave()]];
103
     *
104
     * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
105
     * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_INSERT]], and [[EVENT_AFTER_INSERT]]
106
     * will be raised by the corresponding methods.
107
     *
108
     * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database.
109
     *
110
     * If the table's primary key is auto-incremental and is null during insertion,
111
     * it will be populated with the actual value after insertion.
112
     *
113
     * For example, to insert a customer record:
114
     *
115
     * ```php
116
     * $customer = new Customer;
117
     * $customer->name = $name;
118
     * $customer->email = $email;
119
     * $customer->insert();
120
     * ```
121
     *
122
     * @param bool $runValidation whether to perform validation (calling [[validate()]])
123
     * before saving the record. Defaults to `true`. If the validation fails, the record
124
     * will not be saved to the LDAP and this method will return `false`.
125
     * @param string[]|null $attributes list of attributes that need to be saved.
126
     * Defaults to null, meaning all attributes that are loaded from LDAP will be saved.
127
     * meaning all attributes that are loaded from DB will be saved.
128
     * @return bool whether the attributes are valid and the record is inserted successfully.
129
     */
130
    public function insert($runValidation = true, $attributes = null)
131
    {
132
        if ($runValidation && !$this->validate($attributes)) {
133
            Yii::info('Model not inserted due to validation error.', __METHOD__);
134
            return false;
135
        }
136
137
        return $this->insertInternal($attributes);
138
    }
139
140
    /**
141
     * Inserts an ActiveRecord into LDAP without.
142
     *
143
     * @param  string[]|null $attributes list of attributes that need to be saved. Defaults to null,
144
     * meaning all attributes that are loaded will be saved.
145
     * @return bool whether the record is inserted successfully.
146
     */
147
    protected function insertInternal($attributes = null)
148
    {
149
        if (!$this->beforeSave(true)) {
150
            return false;
151
        }
152
153
        $primaryKey = static::primaryKey();
154
        $values     = $this->getDirtyAttributes($attributes);
155
        $dn         = $values[$primaryKey[0]];
156
        //$dn         = $this->getPrimaryKey();
157
        unset($values[$primaryKey[0]]);
158
159
        static::getDb()->open();
160
161
        if (static::getDb()->add($dn, $values) === false) {
162
            return false;
163
        }
164
        $this->setAttribute($primaryKey[0], $dn);
165
        $values[$primaryKey[0]] = $dn;
166
167
        $changedAttributes = array_fill_keys(array_keys($values), null);
168
        $this->setOldAttributes($values);
169
        $this->afterSave(true, $changedAttributes);
170
171
        static::getDb()->close();
172
173
        return true;
174
    }
175
176
    /**
177
     * @see update()
178
     * @param string[]|null $attributes the names of the attributes to update.
179
     * @return integer number of rows updated
180
     */
181
    protected function updateInternal($attributes = null)
182
    {
183
        if (!$this->beforeSave(false)) {
184
            return false;
185
        }
186
        $values = $this->getDirtyAttributes($attributes);
187
        if (isset($values[self::primaryKey()[0]])) {
188
            unset($values[self::primaryKey()[0]]);
189
        }
190
        if (empty($values)) {
191
            $this->afterSave(false, $values);
192
            return 0;
193
        }
194
195
        if ($this->getOldPrimaryKey() !== $this->getPrimaryKey()) {
196
            static::getDb()->open();
197
            static::getDb()->rename($this->getOldPrimaryKey(), $this->getPrimaryKey(), $newParent, true);
0 ignored issues
show
It seems like $this->getOldPrimaryKey() can also be of type array and array; however, parameter $dn of chrmorandi\ldap\Connection::rename() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

197
            static::getDb()->rename(/** @scrutinizer ignore-type */ $this->getOldPrimaryKey(), $this->getPrimaryKey(), $newParent, true);
Loading history...
Comprehensibility Best Practice introduced by
The variable $newParent seems to be never defined.
Loading history...
It seems like $this->getPrimaryKey() can also be of type array and array; however, parameter $newRdn of chrmorandi\ldap\Connection::rename() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

197
            static::getDb()->rename($this->getOldPrimaryKey(), /** @scrutinizer ignore-type */ $this->getPrimaryKey(), $newParent, true);
Loading history...
198
            static::getDb()->close();
199
            if (!$this->refresh()) {
200
                Yii::info('Model not refresh.', __METHOD__);
201
                return 0;
202
            }
203
        }
204
205
        foreach ($values as $key => $value) {
206
            if (empty($this->getOldAttribute($key)) && $value === '') {
207
                unset($values[$key]);
208
            } elseif ($value === '') {
209
                $attributes[] = ['attrib' => $key, 'modtype' => LDAP_MODIFY_BATCH_REMOVE];
210
            } elseif (empty($this->getOldAttribute($key))) {
211
                $attributes[] = ['attrib' => $key, 'modtype' => LDAP_MODIFY_BATCH_ADD, 'values' => is_array($value) ? array_map('strval', $value) : [(string) $value]];
212
            } else {
213
                $attributes[] = ['attrib' => $key, 'modtype' => LDAP_MODIFY_BATCH_REPLACE, 'values' => is_array($value) ? array_map('strval', $value) : [(string) $value]];
214
            }
215
        }
216
217
        // We do not check the return value of updateAll() because it's possible
218
        // that the UPDATE statement doesn't change anything and thus returns 0.
219
        $rows = static::updateAll($attributes, $condition);
220
221
//        $changedAttributes = [];
222
//        foreach ($values as $key => $value) {
223
//            $changedAttributes[$key] = empty($this->getOldAttributes($key)) ? $this->getOldAttributes($key) : null;
224
//            $this->setOldAttributes([$key=>$value]);
225
//        }
226
//        $this->afterSave(false, $changedAttributes);
227
228
        return $rows;
229
    }
230
231
    /**
232
     * Updates the whole table using the provided attribute values and conditions.
233
     * For example, to change the status to be 1 for all customers whose status is 2:
234
     *
235
     * ```php
236
     * Customer::updateAll(['status' => 1], 'status = 2');
237
     * ```
238
     *
239
     * @param string[] $attributes attribute values (name-value pairs) to be saved into the table
240
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
241
     * Please refer to [[Query::where()]] on how to specify this parameter.
242
     * @return integer the number of rows updated
243
     */
244
    public static function updateAll($attributes, $condition = '')
245
    {
246
        if (is_array($condition)) {
247
            $condition = $condition['dn'];
248
        }
249
250
        static::getDb()->open();
251
        static::getDb()->modify($condition, $attributes);
252
        static::getDb()->close();
253
254
        return count($attributes);
255
    }
256
257
    /**
258
     * Deletes rows in the table using the provided conditions.
259
     * WARNING: If you do not specify any condition, this method will delete ALL rows in the ldap directory.
260
     *
261
     * For example, to delete all customers whose status is 3:
262
     *
263
     * ```php
264
     * Customer::deleteAll('status = 3');
265
     * ```
266
     *
267
     * @param  string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
268
     * Please refer to [[Query::where()]] on how to specify this parameter.
269
     * @return integer the number of rows deleted
270
     */
271
    public static function deleteAll($condition = '')
272
    {
273
        $entries = (new Query())->select(self::primaryKey())->where($condition)->execute()->toArray();
274
        $count   = 0;
275
276
        static::getDb()->open();
277
        foreach ($entries as $entry) {
278
            $dn = $entry[self::primaryKey()[0]];
279
            static::getDb()->delete($dn);
280
            $count++;
281
        }
282
        static::getDb()->close();
283
284
        return $count;
285
    }
286
287
}
288