Ajde_Model::doEncrypt()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
class Ajde_Model extends Ajde_Object_Standard
4
{
5
    const ENCRYPTION_PREFIX = '$$$ENCRYPTED$$$';
6
7
    protected $_connection;
8
    protected $_table;
9
10
    protected $_autoloadParents = false;
11
12
    protected $_tableName;
13
    protected $_displayField = null;
14
    protected $_encrypedFields = [];
15
    protected $_jsonFields = [];
16
17
    protected $_hasMeta = false;
18
    protected $_metaLookup = [];
19
    public $_metaValues = [];
20
21
    protected $_validators = [];
22
23
    public static function extendController(Ajde_Controller $controller, $method, $arguments)
24
    {
25
        // Register getModel($name) function on Ajde_Controller
26
        if ($method === 'getModel') {
27
            if (!isset($arguments[0])) {
28
                $arguments[0] = $controller->getModule();
29
            }
30
31
            return self::getModel($arguments[0]);
32
        }
33
        // TODO: if last triggered in event cueue, throw exception
34
        // throw new Ajde_Exception("Call to undefined method ".get_class($controller)."::$method()", 90006);
35
        // Now, we give other callbacks in event cueue chance to return
36
    }
37
38
    /**
39
     * @param string $name
40
     *
41
     * @return Ajde_Model
42
     */
43
    public static function getModel($name)
44
    {
45
        $modelName = ucfirst($name).'Model';
46
47
        return new $modelName();
48
    }
49
50
    /**
51
     * @return Ajde_Collection
52
     */
53
    public function getCollection()
54
    {
55
        $collectionName = $this->toCamelCase($this->_tableName).'Collection';
56
57
        return new $collectionName();
58
    }
59
60
    public function __construct()
61
    {
62
        if (empty($this->_tableName)) {
63
            $tableNameCC = str_replace('Model', '', get_class($this));
64
            $this->_tableName = $this->fromCamelCase($tableNameCC);
65
        }
66
67
        $this->_connection = Ajde_Db::getInstance()->getConnection();
68
        $this->_table = Ajde_Db::getInstance()->getTable($this->_tableName);
69
    }
70
71
    public function __set($name, $value)
72
    {
73
        $this->set($name, $value);
74
    }
75
76
    protected function _set($name, $value)
77
    {
78
        parent::_set($name, $value);
79
        if ($this->isFieldEncrypted($name)) {
80
            parent::_set($name, $this->encrypt($name));
81
        }
82
    }
83
84
    public function __get($name)
85
    {
86
        return $this->get($name);
87
    }
88
89
    protected function _get($name)
90
    {
91
        if ($this->isFieldEncrypted($name)) {
92
            return $this->decrypt($name);
93
        }
94
95
        return parent::_get($name);
96
    }
97
98
    public function __isset($name)
99
    {
100
        return $this->has($name);
101
    }
102
103
    public function __toString()
104
    {
105
        if (empty($this->_data)) {
106
            return '';
107
        }
108
109
        return (string) $this->getPK();
110
    }
111
112
    public function __sleep()
113
    {
114
        return ['_autoloadParents', '_displayField', '_encrypedFields', '_data'];
115
    }
116
117
    public function __wakeup()
118
    {
119
        $this->__construct();
120
    }
121
122
    public function reset()
123
    {
124
        $this->_metaValues = [];
125
        parent::reset();
126
    }
127
128
    public function isEmpty($key)
129
    {
130
        $value = (string) $this->get($key);
131
132
        return empty($value);
133
    }
134
135
    public function getPK()
136
    {
137
        if (empty($this->_data)) {
138
            return;
139
        }
140
        $pk = $this->getTable()->getPK();
141
142
        return $this->has($pk) ? $this->get($pk) : null;
143
    }
144
145
    public function getDisplayField()
146
    {
147
        if (isset($this->_displayField)) {
148
            return $this->_displayField;
149
        } else {
150
            return current($this->getTable()->getFieldNames());
151
        }
152
    }
153
154
    public function displayField()
155
    {
156
        $displayField = $this->getDisplayField();
157
158
        return $this->has($displayField) ? $this->get($displayField) : '(untitled model)';
159
    }
160
161
    public function getPublishData()
162
    {
163
        return [
164
            'title'   => $this->displayField(),
165
            'message' => null,
166
            'image'   => null,
167
            'url'     => null,
168
        ];
169
    }
170
171
    public function getPublishRecipients()
172
    {
173
        return [];
174
    }
175
176
    /**
177
     * @return Ajde_Db_Adapter_Abstract
178
     */
179
    public function getConnection()
180
    {
181
        return $this->_connection;
182
    }
183
184
    /**
185
     * @return Ajde_Db_Table
186
     */
187
    public function getTable()
188
    {
189
        return $this->_table;
190
    }
191
192
    public function populate($array)
193
    {
194
        // TODO: parse array and typecast to match fields settings
195
        $this->_data = array_merge($this->_data, $array);
196
    }
197
198
    public function getValues($recursive = true)
199
    {
200
        $return = [];
201
        foreach ($this->_data as $k => $v) {
202
            if ($v instanceof self) {
203
                if ($recursive) {
204
                    $return[$k] = $v->getValues();
205
                } else {
206
                    @$return[$k] = (string) $v;
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
207
                }
208
            } else {
209
                $return[$k] = $v;
210
            }
211
        }
212
213
        return $return;
214
    }
215
216
    // Load model values
217
    public function loadByPK($value)
218
    {
219
        $this->reset();
220
        $pk = $this->getTable()->getPK();
221
222
        return $this->loadByFields([$pk => $value]);
223
    }
224
225
    public function loadByField($field, $value)
226
    {
227
        return $this->loadByFields([$field => $value]);
228
    }
229
230
    public function loadByFields($array)
231
    {
232
        $sqlWhere = [];
233
        $values = [];
234
        foreach ($array as $field => $value) {
235
            $sqlWhere[] = $field.' = ?';
236
            if ($this->isFieldEncrypted($field)) {
237
                $values[] = $this->doEncrypt($value);
238
            } else {
239
                $values[] = $value;
240
            }
241
        }
242
        $sql = 'SELECT * FROM '.$this->_table.' WHERE '.implode(' AND ', $sqlWhere).' LIMIT 1';
243
244
        return $this->_load($sql, $values);
245
    }
246
247
    protected function _load($sql, $values, $populate = true)
248
    {
249
        $statement = $this->getConnection()->prepare($sql);
250
        $statement->execute($values);
251
        $result = $statement->fetch(PDO::FETCH_ASSOC);
252
        if ($result === false || empty($result)) {
253
            return false;
254
        } else {
255
            if ($populate === true) {
256
                $this->reset();
257
                $this->loadFromValues($result);
258
            }
259
        }
260
261
        return true;
262
    }
263
264
    public function loadFromValues($values)
265
    {
266
        $this->populate($values);
267
        if ($this->_autoloadParents === true) {
268
            $this->loadParents();
269
        }
270
        if ($this->_hasMeta === true) {
271
            $this->populateMeta();
272
        }
273
    }
274
275
    private function getMetaTable()
276
    {
277
        return $this->getTable().'_meta';
278
    }
279
280
    public function populateMeta($values = false, $override = true)
281
    {
282
        if (!$values) {
283
            $values = $this->getMetaValues();
284
        }
285
        foreach ($values as $metaId => $value) {
0 ignored issues
show
Bug introduced by
The expression $values of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
286
            if ($override || !$this->has('meta_'.$metaId)) {
287
                $this->set('meta_'.$metaId, $value);
288
            }
289
        }
290
    }
291
292
    public function getMetaValues()
293
    {
294
        if (empty($this->_metaValues)) {
295
            $meta = [];
296
            if ($this->hasLoaded()) {
297
                $sql = 'SELECT * FROM '.$this->getMetaTable().' WHERE '.$this->getTable().' = ?';
298
                $statement = $this->getConnection()->prepare($sql);
299
                $statement->execute([$this->getPK()]);
300
                $results = $statement->fetchAll(PDO::FETCH_ASSOC);
301 View Code Duplication
                foreach ($results as $result) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
302
                    if (isset($meta[$result['meta']])) {
303
                        if (is_array($meta[$result['meta']])) {
304
                            $meta[$result['meta']][] = $result['value'];
305
                        } else {
306
                            $meta[$result['meta']] = [
307
                                $meta[$result['meta']],
308
                                $result['value'],
309
                            ];
310
                        }
311
                    } else {
312
                        $meta[$result['meta']] = $result['value'];
313
                    }
314
                }
315
            }
316
            $this->_metaValues = $meta;
317
        }
318
319
        return $this->_metaValues;
320
    }
321
322
    private function fuzzyMetaName($name)
323
    {
324
        return str_replace(' ', '_', strtolower($name));
325
    }
326
327
    public function lookupMetaName($name)
328
    {
329
        if (empty($this->_metaLookup)) {
330
            // We need to have the MetaModel here..
331
            $metaCollection = new MetaCollection();
332
            // disable join, as we don't get any metas which don't have a row yet
333
            //			$metaCollection->addFilter(new Ajde_Filter_Join($this->getMetaTable(), 'meta.id', 'meta'));
334
            foreach ($metaCollection as $meta) {
335
                /* @var $meta MetaModel */
336
                $this->_metaLookup[$this->fuzzyMetaName($meta->get('name'))] = $meta->getPK();
337
            }
338
        }
339
        if (isset($this->_metaLookup[$this->fuzzyMetaName($name)])) {
340
            return $this->_metaLookup[$this->fuzzyMetaName($name)];
341
        }
342
343
        return false;
344
    }
345
346
    public function getMetaValue($metaId)
347
    {
348
        if (!is_numeric($metaId)) {
349
            $metaId = $this->lookupMetaName($metaId);
350
        }
351
        $values = $this->getMetaValues();
352
        if (isset($values[$metaId])) {
353
            return $values[$metaId];
354
        }
355
356
        return false;
357
    }
358
359
    public function getMediaModelFromMetaValue($metaId)
360
    {
361
        $metaValue = (int) $this->getMetaValue($metaId);
362
        $media = new MediaModel();
363
        $media->loadByPK($metaValue);
364
365
        return $media->hasLoaded() ? $media : false;
366
    }
367
368
    public function getNodeModelFromMetaValue($metaId)
369
    {
370
        $metaValue = (int) $this->getMetaValue($metaId);
371
        $node = new NodeModel();
372
        $node->loadByPK($metaValue);
373
374
        return $node->hasLoaded() ? $node : false;
375
    }
376
377
    public function saveMeta()
378
    {
379
        foreach ($this->getValues() as $key => $value) {
380
            // don't ignore empty values
381
            //			if (substr($key, 0, 5) === 'meta_' && $value) {
382
            if (substr($key, 0, 5) === 'meta_') {
383
                $metaId = str_replace('meta_', '', $key);
384
                $this->saveMetaValue($metaId, $value);
385
            }
386
        }
387
    }
388
389
    public function saveMetaValue($metaId, $value)
390
    {
391
        if (!is_numeric($metaId)) {
392
            $metaId = $this->lookupMetaName($metaId);
393
        }
394
395
        $this->deleteMetaValue($metaId);
396
397
        // Get meta stuff
398
        $meta = new MetaModel();
399
        $meta->loadByPK($metaId);
400
        $metaType = Ajde_Crud_Cms_Meta::fromType($meta->getType());
0 ignored issues
show
Documentation Bug introduced by
The method getType does not exist on object<MetaModel>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
401
402
        // Before save
403
        $value = $metaType->beforeSave($meta, $value, $this);
404
405
        // Insert new ones
406
        $sql = 'INSERT INTO '.$this->getMetaTable().' ('.$this->getTable().', meta, VALUE) VALUES (?, ?, ?)';
407
        $statement = $this->getConnection()->prepare($sql);
408
        $statement->execute([$this->getPK(), $metaId, $value]);
409
410
        // After save
411
        $metaType->afterSave($meta, $value, $this);
412
413
        $this->_metaValues[$metaId] = $value;
414
    }
415
416
    public function deleteMetaValue($metaId)
417
    {
418
        if (!is_numeric($metaId)) {
419
            $metaId = $this->lookupMetaName($metaId);
420
        }
421
422
        // Delete old records
423
        $sql = 'DELETE FROM '.$this->getMetaTable().' WHERE '.$this->getTable().' = ? AND meta = ?';
424
        $statement = $this->getConnection()->prepare($sql);
425
        $statement->execute([$this->getPK(), $metaId]);
426
    }
427
428
    /**
429
     * @param array $fields
430
     */
431
    public function setEncryptedFields($fields)
432
    {
433
        $this->_encrypedFields = $fields;
434
    }
435
436
    /**
437
     * @param string $field
438
     */
439
    public function addEncryptedField($field)
440
    {
441
        $this->_encrypedFields[] = $field;
442
    }
443
444
    /**
445
     * @return array
446
     */
447
    public function getEncryptedFields()
448
    {
449
        return $this->_encrypedFields;
450
    }
451
452
    /**
453
     * @param string $field
454
     *
455
     * @return bool
456
     */
457
    public function isFieldEncrypted($field)
458
    {
459
        return in_array($field, $this->getEncryptedFields());
460
    }
461
462
    /**
463
     * @return array
464
     */
465
    public function getJsonFields()
466
    {
467
        return $this->_jsonFields;
468
    }
469
470
    /**
471
     * @param string $field
472
     *
473
     * @return bool
474
     */
475
    public function isFieldJson($field)
476
    {
477
        return in_array($field, $this->getJsonFields());
478
    }
479
480
    /**
481
     * @return bool
482
     */
483
    public function save()
484
    {
485
        if (method_exists($this, 'beforeSave')) {
486
            $this->beforeSave();
0 ignored issues
show
Documentation Bug introduced by
The method beforeSave does not exist on object<Ajde_Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
487
        }
488
        $pk = $this->getTable()->getPK();
489
490
        $sqlSet = [];
491
        $values = [];
492
493
        // encryption
494
        foreach ($this->getEncryptedFields() as $field) {
495
            if ($this->has($field)) {
496
                parent::_set($field, $this->encrypt($field));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_set() instead of save()). Are you sure this is correct? If so, you might want to change this to $this->_set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
497
            }
498
        }
499
500
        foreach ($this->getTable()->getFieldNames() as $field) {
501
            // Don't save a field is it's empty or not set
502
            if ($this->has($field)) {
503
                if ($this->getTable()->getFieldProperties($field,
504
                        'type') === Ajde_Db::FIELD_TYPE_DATE && parent::_get($field) instanceof DateTime
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of save()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
505
                ) {
506
                    $sqlSet[] = $field.' = ?';
507
                    $values[] = parent::_get($field)->format('Y-m-d h:i:s');
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of save()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
508
                } elseif ($this->getTable()->getFieldProperties($field,
509
                        'isAutoUpdate') && !$this->get($field) instanceof Ajde_Db_Function
510
                ) {
511
                    // just ignore this field
512
                } elseif ($this->isEmpty($field) && !$this->getTable()->getFieldProperties($field, 'isRequired')) {
513
                    $sqlSet[] = $field.' = NULL';
514
                } elseif (!$this->isEmpty($field)) {
515
                    if ($this->get($field) instanceof Ajde_Db_Function) {
516
                        $sqlSet[] = $field.' = '.(string) $this->get($field);
517 View Code Duplication
                    } elseif ($this->getTable()->getFieldProperties($field, 'type') === Ajde_Db::FIELD_TYPE_SPATIAL) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
518
                        $pointValues = explode(' ', (string) parent::_get($field));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of save()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
519
                        $sqlSet[] = $field.' = PointFromWKB(POINT('.str_replace(',', '.',
520
                                (float) $pointValues[0]).','.str_replace(',', '.', (float) $pointValues[1]).'))';
521
                    } else {
522
                        $sqlSet[] = $field.' = ?';
523
                        $value = parent::_get($field);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of save()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
524
                        if (is_float($value)) {
525
                            $values[] = $value;
526
                        } else {
527
                            $values[] = (string) $value;
528
                        }
529
                    }
530
                } elseif ($this->get($field) === 0 || $this->get($field) === '0') {
531
                    $sqlSet[] = $field.' = ?';
532
                    $values[] = (string) parent::_get($field);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of save()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
533
                } else {
534
                    // Field is required but has an empty value..
535
                    // (shouldn't have passed validation)
536
                    // TODO: set to empty string or ignore?
537
                }
538
            }
539
        }
540
        $values[] = $this->getPK();
541
        $sql = 'UPDATE '.$this->_table.' SET '.implode(', ', $sqlSet).' WHERE '.$pk.' = ?';
542
        $statement = $this->getConnection()->prepare($sql);
543
        $return = $statement->execute($values);
544
        if ($this->_hasMeta === true) {
545
            $this->saveMeta();
546
        }
547
        if (method_exists($this, 'afterSave')) {
548
            $this->afterSave();
0 ignored issues
show
Documentation Bug introduced by
The method afterSave does not exist on object<Ajde_Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
549
        }
550
551
        return $return;
552
    }
553
554
    public function insert($pkValue = null, $skipBeforeInsert = false)
555
    {
556
        if (method_exists($this, 'beforeInsert') && $skipBeforeInsert === false) {
557
            $this->beforeInsert();
0 ignored issues
show
Bug introduced by
The method beforeInsert() does not exist on Ajde_Model. Did you maybe mean insert()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
558
        }
559
        $pk = $this->getTable()->getPK();
560
        if (isset($pkValue)) {
561
            $this->set($pk, $pkValue);
562
        } else {
563
            $this->set($pk, null);
564
        }
565
        $sqlFields = [];
566
        $sqlValues = [];
567
        $values = [];
568
569
        // encryption
570
        foreach ($this->getEncryptedFields() as $field) {
571
            if ($this->has($field)) {
572
                parent::_set($field, $this->encrypt($field));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_set() instead of insert()). Are you sure this is correct? If so, you might want to change this to $this->_set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
573
            }
574
        }
575
576
        foreach ($this->getTable()->getFieldNames() as $field) {
577
            // Don't save a field is it's empty or not set
578
            if ($this->has($field) && $this->getTable()->getFieldProperties($field,
579
                    'type') === Ajde_Db::FIELD_TYPE_DATE && parent::_get($field) instanceof DateTime
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of insert()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
580
            ) {
581
                $sqlFields[] = $field;
582
                $sqlValues[] = '?';
583
                $values[] = parent::_get($field)->format('Y-m-d h:i:s');
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of insert()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
584
            } else {
585
                if ($this->has($field) && !$this->isEmpty($field)) {
586
                    if ($this->get($field) instanceof Ajde_Db_Function) {
587
                        $sqlFields[] = $field;
588
                        $sqlValues[] = (string) $this->get($field);
589 View Code Duplication
                    } elseif ($this->getTable()->getFieldProperties($field, 'type') === Ajde_Db::FIELD_TYPE_SPATIAL) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
                        $sqlFields[] = $field;
591
                        $pointValues = explode(' ', (string) parent::_get($field));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of insert()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
592
                        $sqlValues[] = 'PointFromWKB(POINT('.str_replace(',', '.',
593
                                (float) $pointValues[0]).','.str_replace(',', '.', (float) $pointValues[1]).'))';
594
                    } else {
595
                        $sqlFields[] = $field;
596
                        $sqlValues[] = '?';
597
                        $value = parent::_get($field);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of insert()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
598
                        if (is_float($value)) {
599
                            $values[] = $value;
600
                        } else {
601
                            $values[] = (string) $value;
602
                        }
603
                    }
604
                } else {
605
                    if ($this->has($field) && ($this->get($field) === 0 || $this->get($field) === '0')) {
606
                        $sqlFields[] = $field;
607
                        $sqlValues[] = '?';
608
                        $values[] = (string) parent::_get($field);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of insert()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
609
                    } else {
610
                        parent::_set($field, null);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_set() instead of insert()). Are you sure this is correct? If so, you might want to change this to $this->_set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
611
                    }
612
                }
613
            }
614
        }
615
        $sql = 'INSERT INTO '.$this->_table.' ('.implode(', ', $sqlFields).') VALUES ('.implode(', ',
616
                $sqlValues).')';
617
        $statement = $this->getConnection()->prepare($sql);
618
        $return = $statement->execute($values);
619
        if (!isset($pkValue)) {
620
            $this->set($pk, $this->getConnection()->lastInsertId());
621
        }
622
        if ($this->_hasMeta === true) {
623
            $this->saveMeta();
624
        }
625
        if (method_exists($this, 'afterInsert')) {
626
            $this->afterInsert();
0 ignored issues
show
Bug introduced by
The method afterInsert() does not exist on Ajde_Model. Did you maybe mean insert()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
627
        }
628
629
        return $return;
630
    }
631
632
    public function delete()
633
    {
634
        if (method_exists($this, 'beforeDelete')) {
635
            $this->beforeDelete();
0 ignored issues
show
Bug introduced by
The method beforeDelete() does not exist on Ajde_Model. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
636
        }
637
        $id = $this->getPK();
638
        $pk = $this->getTable()->getPK();
639
        $sql = 'DELETE FROM '.$this->_table.' WHERE '.$pk.' = ? LIMIT 1';
640
        $statement = $this->getConnection()->prepare($sql);
641
        try {
642
            $return = $statement->execute([$id]);
643
        } catch (Ajde_Db_IntegrityException $e) {
644
            return false;
645
        }
646
        if (method_exists($this, 'afterDelete')) {
647
            $this->afterDelete();
0 ignored issues
show
Bug introduced by
The method afterDelete() does not exist on Ajde_Model. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
648
        }
649
650
        return $return;
651
    }
652
653
    public function hasParent($parent)
654
    {
655
        if (!$parent instanceof Ajde_Db_Table) {
656
            // throws error if no table can be found
657
            $parent = new Ajde_Db_Table($parent);
658
        }
659
        $fk = $this->getTable()->getFK($parent);
660
661
        return $fk;
662
    }
663
664
    public function getParents()
665
    {
666
        return $this->getTable()->getParents();
667
    }
668
669
    public function exists()
670
    {
671
        return (bool) $this->getPK();
672
    }
673
674
    public function hasLoaded()
675
    {
676
        return !empty($this->_data);
677
    }
678
679
    public function hasParentLoaded($parent)
680
    {
681
        return $this->has($parent) && $this->get($parent) instanceof self && $this->get($parent)->hasLoaded();
682
    }
683
684
    public function loadParents()
685
    {
686
        foreach ($this->getParents() as $column) {
687
            $this->loadParent($column);
688
        }
689
    }
690
691
    public function loadParent($column)
692
    {
693
        if (empty($this->_data)) {
694
            // TODO:
695
            throw new Ajde_Exception('Model '.(string) $this->getTable().' not loaded when loading parent');
696
        }
697
        if ($this->hasParentLoaded($column)) {
698
            return;
699
        }
700
        if (!$this->has($column)) {
701
            // No value for FK field
702
            return false;
703
        }
704
        $parentModel = $this->getParentModel($column);
705
        if ($parentModel === false) {
706
            // TODO:
707
            throw new Ajde_Exception('Could not load parent model \''.$column."'");
708
        }
709
        if ($parentModel->getTable()->getPK() != $this->getParentField($column)) {
710
            // TODO:
711
            throw new Ajde_Exception('Constraints on non primary key fields are currently not supported');
712
        }
713
        $parentModel->loadByPK($this->get($column));
714
        $this->set($column, $parentModel);
715
    }
716
717
    /**
718
     * @param string $column
719
     *
720
     * @return Ajde_Model
721
     */
722
    public function getParentModel($column)
723
    {
724
        $parentModelName = ucfirst($this->getParentTable($column)).'Model';
725
        if (!class_exists($parentModelName)) {
726
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Ajde_Model::getParentModel of type Ajde_Model.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
727
        }
728
729
        return new $parentModelName();
730
    }
731
732
    public function getParentTable($column)
733
    {
734
        $fk = $this->getTable()->getFK($column);
735
736
        return strtolower($fk['parent_table']);
737
    }
738
739
    public function getParentField($column)
740
    {
741
        $fk = $this->getTable()->getFK($column);
742
743
        return strtolower($fk['parent_field']);
744
    }
745
746
    public function getAutoloadParents()
747
    {
748
        return $this->_autoloadParents;
749
    }
750
751
    public function addValidator($fieldName, Ajde_Model_ValidatorAbstract $validator)
752
    {
753
        $validator->setModel($this);
754
        if (!isset($this->_validators[$fieldName])) {
755
            $this->_validators[$fieldName] = [];
756
        }
757
        $this->_validators[$fieldName][] = $validator;
758
    }
759
760
    public function getValidators()
761
    {
762
        return $this->_validators;
763
    }
764
765
    public function getValidatorsFor($fieldName)
766
    {
767
        if (!isset($this->_validators[$fieldName])) {
768
            $this->_validators[$fieldName] = [];
769
        }
770
771
        return $this->_validators[$fieldName];
772
    }
773
774
    public function validate($fieldOptions = [])
775
    {
776
        if (method_exists($this, 'beforeValidate')) {
777
            $return = $this->beforeValidate();
0 ignored issues
show
Bug introduced by
The method beforeValidate() does not exist on Ajde_Model. Did you maybe mean validate()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
778
            if ($return !== true && $return !== false) {
779
                // TODO:
780
                throw new Ajde_Exception(sprintf('beforeValidate() must return either TRUE or FALSE'));
781
            }
782
            if ($return === false) {
783
                return false;
784
            }
785
        }
786
787
        if (!$this->hasLoaded()) {
788
            return false;
789
        }
790
        $errors = [];
791
        $validator = $this->_getValidator();
792
793
        $valid = $validator->validate($fieldOptions);
794
        if (!$valid) {
795
            $errors = $validator->getErrors();
796
        }
797
        $this->setValidationErrors($errors);
0 ignored issues
show
Documentation Bug introduced by
The method setValidationErrors does not exist on object<Ajde_Model>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
798
799
        if (method_exists($this, 'afterValidate')) {
800
            $this->afterValidate();
0 ignored issues
show
Bug introduced by
The method afterValidate() does not exist on Ajde_Model. Did you maybe mean validate()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
801
        }
802
803
        return $valid;
804
    }
805
806
    public function getValidationErrors()
807
    {
808
        return parent::getValidationErrors();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Ajde_Object_Standard as the method getValidationErrors() does only exist in the following sub-classes of Ajde_Object_Standard: AclModel, Ajde_Acl, Ajde_Acl_Proxy_Model, Ajde_Lang_Proxy_Model, Ajde_Model, Ajde_Model_Revision, Ajde_Model_With_Acl, Ajde_Model_With_AclI18n, Ajde_Model_With_AclI18nRevision, Ajde_Model_With_AclRevision, Ajde_Model_With_I18n, Ajde_Model_With_Revision, Ajde_Shop_Cart, Ajde_Shop_Cart_Item, Ajde_Shop_Transaction, Ajde_User, CartItemModel, CartModel, EmailModel, FormModel, LogModel, MailerlogModel, MediaModel, MediatypeModel, MenuModel, MetaModel, NodeModel, NodetypeModel, ProductModel, RevisionModel, SettingModel, SsoModel, SubmissionModel, TagModel, TemplateModel, TestModel, TransactionItemModel, TransactionModel, UserModel, UsergroupModel, VatModel. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
809
    }
810
811
    /**
812
     * @return Ajde_Model_Validator
813
     */
814
    private function _getValidator()
815
    {
816
        return new Ajde_Model_Validator($this);
817
    }
818
819
    public function hash()
820
    {
821
        $str = implode('', $this->valuesAsSingleDimensionArray());
822
823
        return md5($str);
824
    }
825
826
    public function isEncrypted($field)
827
    {
828
        return substr_count(Ajde_Component_String::decrypt(parent::_get($field)),
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of isEncrypted()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
829
            self::ENCRYPTION_PREFIX.config('security.secret'));
830
    }
831
832
    public function encrypt($field)
833
    {
834
        if (!$this->isEmpty($field)) {
835
            if ($this->isEncrypted($field)) {
836
                return parent::_get($field);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of encrypt()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
837
            }
838
839
            return $this->doEncrypt(parent::_get($field));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of encrypt()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
840
        }
841
    }
842
843
    public function doEncrypt($string)
844
    {
845
        return Ajde_Component_String::encrypt(self::ENCRYPTION_PREFIX.config('security.secret').$string);
846
    }
847
848
    public function decrypt($field)
849
    {
850
        if (!$this->isEncrypted($field)) {
851
            return parent::_get($field);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of decrypt()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
852
        }
853
        $decrypted = str_replace(self::ENCRYPTION_PREFIX.config('security.secret'), '',
854
            Ajde_Component_String::decrypt(parent::_get($field)));
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (_get() instead of decrypt()). Are you sure this is correct? If so, you might want to change this to $this->_get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
855
856
        return $decrypted;
857
    }
858
859
    // TREE SORT FUNCTIONS
860
861
    public function sortTree($collectionName, $parentField = 'parent', $levelField = 'level', $sortField = 'sort')
862
    {
863
        $collection = new $collectionName();
864
        $collection->addFilter(new Ajde_Filter_Where($parentField, Ajde_Filter::FILTER_IS, null));
865
        $collection->orderBy($sortField);
866
867
        // Start at root path
868
        $this->_recurseChildren($collection, $collectionName, $parentField, $levelField, $sortField);
869
    }
870
871
    private function _recurseChildren(
872
        $collection,
873
        $collectionName,
874
        $parentField,
875
        $levelField,
876
        $sortField,
877
        $updatedField = 'updated'
878
    ) {
879
        static $sort;
880
        static $level;
881
        /** @var $item Ajde_Acl_Proxy_Model */
882
        foreach ($collection as $item) {
883
            $sort++;
884
            $item->set($sortField, $sort);
885
            $item->set($levelField, $level);
886
            $item->set($updatedField, new Ajde_Db_Function($updatedField));
887
            if ($item instanceof Ajde_Acl_Proxy_Model) {
888
                if ($item->validateAccess('update', false)) {
889
                    $item->save();
890
                }
891
            } else {
892
                $item->save();
893
            }
894
            // Look for children
895
            $children = new $collectionName();
896
            $children->addFilter(new Ajde_Filter_Where($parentField, Ajde_Filter::FILTER_EQUALS, $item->getPK()));
897
            $children->orderBy($sortField);
898
            if ($children->count()) {
899
                $level++;
900
                $this->_recurseChildren($children, $collectionName, $parentField, $levelField, $sortField);
901
            }
902
        }
903
        $level--;
904
    }
905
}
906