Completed
Push — master ( b894a2...c7c535 )
by 紘己
01:47
created

ChangelogBehavior::implementedEvents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 0
1
<?php
2
3
namespace Changelog\Model\Behavior;
4
5
use ArrayObject;
6
use Cake\Database\Type;
7
use Cake\Datasource\EntityInterface;
8
use Cake\Event\Event;
9
use Cake\ORM\Association;
10
use Cake\ORM\Association\BelongsTo;
11
use Cake\ORM\Behavior;
12
use Cake\ORM\Locator\LocatorAwareTrait;
13
use Cake\ORM\Table;
14
use Cake\Utility\Hash;
15
use Exception;
16
use UnexpectedValueException;
17
18
/**
19
 * TODO: impl
20
 *
21
 * Licensed under The MIT License
22
 * For full copyright and license information, please see the LICENSE file
23
 */
24
class ChangelogBehavior extends Behavior
25
{
26
27
    use LocatorAwareTrait;
28
29
    /**
30
     * Default config
31
     *
32
     * These are merged with user-provided configuration when the behavior is used.
33
     *
34
     * @var array
35
     */
36
    protected $_defaultConfig = [
37
        'autoSave' => true,
38
        'changelogTable' => 'Changelog.Changelogs',
39
        'columnTable' => 'Changelog.ChangelogColumns',
40
        'collectChangeOnBeforeSave' => true,
41
        'combinations' => [],
42
        'convertAssociations' => true,
43
        'convertForeignKeys' => true,
44
        'convertDatetimeColumns' => true,
45
        'equalComparison' => true,
46
        'exchangeForeignKey' => true,
47
        'filterForeignKeys' => true,
48
        'ignoreColumns' => [
49
            'id',
50
            'created',
51
            'modified'
52
        ],
53
        'logIsNew' => false,
54
        'logEmptyChanges' => false,
55
        'saveChangelogOnAfterSave' => true,
56
        'tableLocator' => null
57
    ];
58
59
    /**
60
     * Holds collected before values
61
     *
62
     * @var array
63
     */
64
    protected $_collectedBeforeValues = null;
65
66
    /**
67
     * Holds changes to save
68
     *
69
     * @var array
70
     */
71
    protected $_changes = [];
72
73
    /**
74
     * olds combination columns
75
     *
76
     * @var array
77
     */
78
    protected $_combinationColumns = [];
79
80
    /**
81
     * Initialize tableLocator also.
82
     *
83
     * {@inheritdoc}
84
     */
85
    public function initialize(array $config = [])
86
    {
87
        parent::initialize($config);
88
        if ($tableLocator = $this->config('tableLocator')) {
89
            $this->tableLocator($tableLocator);
90
        }
91
    }
92
93
    /**
94
     * beforeSave callback.
95
     * Collects original values of associations.
96
     *
97
     * {@inheritdoc}
98
     */
99
    public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
100
    {
101
        if ($this->config('collectChangeOnBeforeSave')) {
102
            $this->collectChanges($entity, $options);
103
        }
104
    }
105
106
    public function collectChanges(EntityInterface $entity, ArrayObject $options = null)
107
    {
108
        /**
109
         * Custom before values can be set via save options.
110
         */
111
        if ($options && isset($options['collectedBeforeValues'])) {
112
            $this->_collectedBeforeValues = $options['collectedBeforeValues'];
113
        } else {
114
            $this->collectChangelogBeforeValues($entity);
115
        }
116
117
        $Changelogs = $this->getChangelogTable();
0 ignored issues
show
Unused Code introduced by
$Changelogs is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
118
        $Columns = $this->getColumnTable();
119
        $table = $this->_table;
120
121
        /**
122
         * Be sure whether log new entity or not.
123
         */
124
        if (!$this->config('logIsNew') && $entity->isNew()) {
125
            return false;
126
        }
127
128
        /**
129
         * Extract column names from original values
130
         */
131
        $columns = array_keys($entity->getOriginalValues());
132
133
        /**
134
         * Extract dirty columns.
135
         * Exchange columns to actually dirty ones.
136
         */
137
        $afterValues = $entity->extract($columns, $isDirty = true);
138
        $columns = array_keys($afterValues);
139
140
        /**
141
         * Adds extra columns when combinations was given.
142
         */
143
        $this->_combinationColumns = [];
144
        if ($combinations = $this->config('combinations')) {
145
            foreach ($combinations as $name => $settings) {
146
                $settings = $this->_normalizeCombinationSettings($settings);
147
                $this->_combinationColumns = array_merge($this->_combinationColumns, $settings['columns']);
148
            }
149
            $this->_combinationColumns = array_values(array_unique($this->_combinationColumns));
150
151
            $columns = array_values(array_unique(array_merge($columns, $this->_combinationColumns)));
152
            $afterValues = $entity->extract($columns);
153
        }
154
155
        /**
156
         * Extract original value from decided columns.
157
         */
158
        $beforeValues = $entity->extractOriginal($columns);
0 ignored issues
show
Bug introduced by
The method extractOriginal() does not exist on Cake\Datasource\EntityInterface. Did you maybe mean extract()?

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...
159
160
        /**
161
         * Filters ignored columns
162
         */
163
        $columns = array_diff($columns, $this->config('ignoreColumns'));
164
165
        /**
166
         * Exception, before counts should equal to after counts
167
         */
168
        if (count($beforeValues) !== count($afterValues)) {
169
            return false;
170
        }
171
172
        /**
173
         * Filters changes
174
         */
175
        $changes = [];
176
        $associations = $this->_associationsIndexedByProperty();
177
        $foreignKeys = $this->_associationsForForeignKey();
178
        foreach ($columns as $column) {
179
            /**
180
             * Prepare values for events
181
             */
182
            $before = $beforeValues[$column];
183
            $after = $afterValues[$column];
184
185
            $isAssociation = array_key_exists($column, $associations);
186
            $isForeignKey = array_key_exists($column, $foreignKeys);
187
188
            /**
189
             * Prepare association/column info
190
             */
191
            if ($isAssociation) {
192
                $columnDef = null;
193
                $association = $associations[$column];
194
            } else {
195
                $columnDef = $table->schema()->column($column);
196
                $association = null;
197
                if ($isForeignKey) {
198
                    $association = $foreignKeys[$column];
199
                }
200
            }
201
202
            /**
203
             * Event data. These variables can be changed via registered events.
204
             */
205
            $eventData = new ArrayObject(compact([
206
                'entity',
207
                'isForeignKey',
208
                'isAssociation',
209
                'before',
210
                'after',
211
                'beforeValues',
212
                'afterValues',
213
                'column',
214
                'columnDef',
215
                'association',
216
                'table',
217
                'Columns'
218
            ]));
219
220
            /**
221
             * Dispatches convert event
222
             */
223
            $event = $table->dispatchEvent('Changelog.convertValues', [$eventData]);
0 ignored issues
show
Unused Code introduced by
$event is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
224
            extract((array)$eventData);
0 ignored issues
show
Bug introduced by
(array) $eventData cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
225
226
            if (!$this->_combinationColumns || !in_array($column, $this->_combinationColumns)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_combinationColumns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
227
                /**
228
                 * Dispatches filter event
229
                 */
230
                $event = $table->dispatchEvent('Changelog.filterChanges', [$eventData]);
231
                if (!$event->result) {
232
                    continue;
233
                }
234
            }
235
236
            /**
237
             * Determine changes from result
238
             */
239
            extract((array)$eventData);
0 ignored issues
show
Bug introduced by
(array) $eventData cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
240
            $changes[] = [
241
                'column' => $column,
242
                'before' => $before,
243
                'after' => $after,
244
            ];
245
        }
246
247
        /**
248
         * Make combinations
249
         */
250
        if ($combinations = $this->config('combinations')) {
251
            $changes = $this->makeChangelogCombinations($entity, $changes, $combinations);
252
        }
253
254
        return $this->_changes = $changes;
255
    }
256
257
    public function makeChangelogCombinations(EntityInterface $entity, array $changes, array $combinations)
258
    {
259
        $indexedByColumn = collection($changes)->indexBy('column')->toArray();
260
        $removeKeys = [];
261
        $foreignKeys = $this->_associationsForForeignKey();
262
        foreach ($combinations as $name => $settings) {
263
            $settings = $this->_normalizeCombinationSettings($settings);
264
265
            $values = [];
266
            foreach ($settings['columns'] as $column) {
267
                $removeKeys[] = $column;
268
                if (!isset($indexedByColumn[$column])) {
269
                    /**
270
                     * convert foreign keys
271
                     */
272
                    if (isset($foreignKeys[$column])) {
273
                        $association = $foreignKeys[$column];
274
                        $property = $association->property();
275
                        if (isset($indexedByColumn[$property]['after'])) {
276
                            if (isset($indexedByColumn[$property]['before'])) {
277
                                $values['before'][$column] = $indexedByColumn[$property]['before'];
278
                            } else {
279
                                $values['before'][$column] = $indexedByColumn[$property]['after'];
280
                            }
281
                            $values['after'][$column] = $indexedByColumn[$property]['after'];
282
                        } else {
283
                            if (isset($indexedByColumn[$column]['before'])) {
284
                                $values['before'][$column] = $indexedByColumn[$column]['before'];
285
                            } else {
286
                                $values['before'][$column] = $indexedByColumn[$column]['after'];
287
                            }
288
                            $values['after'][$column] = $indexedByColumn[$column]['after'];
289
                        }
290
                        $removeKeys[] = $column;
291
                        $removeKeys[] = $property;
292
                    } else {
293
                        $values['before'][$column] = $entity->get($column);
294
                        $values['after'][$column] = $entity->get($column);
295
                    }
296
                } else {
297
                    /**
298
                     * convert foreign keys
299
                     */
300
                    if (isset($foreignKeys[$column])) {
301
                        $association = $foreignKeys[$column];
302
                        $property = $association->property();
303
                        if (isset($indexedByColumn[$property]['after'])) {
304
                            if (isset($indexedByColumn[$property]['before'])) {
305
                                $values['before'][$column] = $indexedByColumn[$property]['before'];
306
                            } else {
307
                                $values['before'][$column] = $indexedByColumn[$property]['after'];
308
                            }
309
                            $values['after'][$column] = $indexedByColumn[$property]['after'];
310
                            $removeKeys[] = $property;
311
                        } else {
312
                            if (isset($indexedByColumn[$column]['before'])) {
313
                                $values['before'][$column] = $indexedByColumn[$column]['before'];
314
                            } else {
315
                                $values['before'][$column] = $indexedByColumn[$column]['after'];
316
                            }
317
                            $values['after'][$column] = $indexedByColumn[$column]['after'];
318
                        }
319
                    } else {
320
                        $values['before'][$column] = $indexedByColumn[$column]['before'];
321
                        $values['after'][$column] = $indexedByColumn[$column]['after'];
322
                    }
323
                }
324
            }
325
326
            if (isset($settings['convert'])) {
327
                $convert = $settings['convert'];
328
                $converted = $convert($name, $values['before'], $values['after']);
329
            } else {
330
                $converted = [
331
                    'column' => $name,
332
                    'before' => implode(' ', array_filter($values['before'])),
333
                    'after' => implode(' ', array_filter($values['after'])),
334
                ];
335
            }
336
337
            if ($converted['before'] == $converted['after']) {
338
                continue;
339
            }
340
            $indexedByColumn[$name] = $converted;
341
        }
342
343
        $removeKeys = array_diff($removeKeys, array_keys($combinations));
344
        $indexedByColumn = array_diff_key($indexedByColumn, array_flip($removeKeys));
345
        return array_values($indexedByColumn);
346
    }
347
348
    protected function _normalizeCombinationSettings($settings)
349
    {
350
        if (!is_array($settings)) {
351
            throw new UnexpectedValueException(__d('changelog', 'Changelog: `combinations` option should be array'));
352
        }
353
354
        /**
355
         * If numric keys e.g. ['first_name', 'last_name'] given, Handles it
356
         * as a list of columns.
357
         */
358
        if (Hash::numeric(array_keys($settings))) {
359
            $settings = ['columns' => $settings];
360
        }
361
362
        if (!isset($settings['columns']) || !is_array($settings['columns'])) {
363
            throw new UnexpectedValueException(__d('changelog', 'Changelog: `combinations` option should have `columns` key and value as array of columns'));
364
        }
365
366
        return $settings;
367
    }
368
369
    public function collectChangelogBeforeValues($entity)
370
    {
371
        $this->_collectedBeforeValues = [];
372
        $associations = $this->_associationsIndexedByProperty();
373
        foreach ($entity->getOriginalValues() as $key => $value) {
374
            if (isset($associations[$key])) {
375
                $association = $associations[$key];
376
                if (in_array($association->type(), [Association::MANY_TO_MANY, Association::ONE_TO_MANY])) {
377
                    $values = (array)$value;
378
                    foreach ($values as $i => $v) {
379
                        if ($v instanceof EntityInterface) {
380
                            $values[$i] = $v->getOriginalValues();
381
                        } else {
382
                            $values[$i] = $v;
383
                        }
384
                    }
385
                    $this->_collectedBeforeValues[$key] = $values;
386
                } else {
387
                    if ($value instanceof EntityInterface) {
388
                        $this->_collectedBeforeValues[$key] = $value->getOriginalValues();
389
                    } else {
390
                        $this->_collectedBeforeValues[$key] = $value;
391
                    }
392
                }
393
            }
394
        }
395
396
        return $this->_collectedBeforeValues;
397
    }
398
399
    /**
400
     * afterSave callback.
401
     * This logs entities when `onAfterSave` option was turned on.
402
     *
403
     * {@inheritdoc}
404
     */
405
    public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
406
    {
407
        if ($this->config('saveChangelogOnAfterSave')) {
408
            $this->saveChangelog($entity, $this->_changes);
409
        }
410
    }
411
412
    /**
413
     * Saves changelogs for entity.
414
     *
415
     * @param \Cake\Datasource\EntityInterface $entity The entity object to log changes.
416
     * @return \Cake\Datasource\EntityInterface|bool Entity object when logged otherwise `false`.
417
     */
418
    public function saveChangelog(EntityInterface $entity, $changes = [])
419
    {
420
        /**
421
         * Be sure whether change was done or not
422
         */
423
        if (!$this->config('logEmptyChanges') && empty($changes)) {
424
            return false;
425
        }
426
427
        /**
428
         * Saves actually
429
         */
430
        $data = new ArrayObject([
431
            'model' => $this->_table->alias(),
432
            'foreign_key' => $entity->get($this->_table->primaryKey()),
0 ignored issues
show
Bug introduced by
It seems like $this->_table->primaryKey() targeting Cake\ORM\Table::primaryKey() can also be of type array; however, Cake\Datasource\EntityInterface::get() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
433
            'is_new' => $entity->isNew(),
434
            'changelog_columns' => $changes,
435
        ]);
436
        $options = new ArrayObject([
437
            'associated' => 'ChangelogColumns',
438
            'atomic' => false
439
        ]);
440
441
        $Changelogs = $this->getChangelogTable();
442
        return $this->_table->dispatchEvent('Changelog.saveChangelogRecords', compact('Changelogs', 'data', 'options'))->result;
443
    }
444
445
    /**
446
     * Helper method to get table associations array
447
     * indexed by these properties.
448
     *
449
     * @return \Cake\ORM\Association[]
450
     */
451
    protected function _associationsIndexedByProperty()
452
    {
453
        return collection($this->_table->associations())
454
            ->combine(function ($association) {
455
                return $association->property();
456
            }, function ($association) {
457
                return $association;
458
            })->toArray();
459
    }
460
461
    /**
462
     * Helper method to get associations array that table has
463
     * foreign key (means BelongsTo) indexed by foreign key.
464
     *
465
     * @return \Cake\ORM\Association[]
466
     */
467
    protected function _associationsForForeignKey()
468
    {
469
        return collection($this->_table->associations())
470
            ->filter(function ($association) {
471
                return $association instanceof BelongsTo;
472
            })->combine(function ($association) {
473
                return $association->foreignKey();
474
            }, function ($association) {
475
                return $association;
476
            })->toArray();
477
    }
478
479
    /**
480
     * Returns changelogs table
481
     *
482
     * @return \Cake\ORM\Table
483
     */
484
    public function getChangelogTable()
485
    {
486
        return $this->tableLocator()->get($this->config('changelogTable'));
487
    }
488
489
    /**
490
     * Returns changelogs table
491
     *
492
     * @return \Cake\ORM\Table
493
     */
494
    public function getColumnTable()
495
    {
496
        return $this->tableLocator()->get($this->config('columnTable'));
497
    }
498
499
    /**
500
     * Define additional events for filter
501
     *
502
     * {@inheritdoc}
503
     */
504
    public function implementedEvents()
505
    {
506
        return parent::implementedEvents() + [
507
            'Changelog.convertValues' => 'convertChangelogValues',
508
            'Changelog.filterChanges' => 'filterChanges',
509
            'Changelog.saveChangelogRecords' => 'saveChangelogRecords',
510
        ];
511
    }
512
513
    /**
514
     * Default convert process
515
     *
516
     * @param \Cake\Event\Event $event The event for callback
517
     * @param ArrayObject $data The event data. contains:
518
     *                          - entity
519
     *                          - isForeignKey
520
     *                          - isAssociation
521
     *                          - before
522
     *                          - after
523
     *                          - beforeValues
524
     *                          - afterValues
525
     *                          - column
526
     *                          - columnDef
527
     *                          - table
528
     *                          - Columns
529
     * @return array couple of $before, $after
530
     */
531
    public function convertChangelogValues(Event $event, ArrayObject $data)
532
    {
533
        extract((array)$data);
0 ignored issues
show
Bug introduced by
(array) $data cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
534
        /**
535
         * @var \Cake\ORM\Entity $entity
536
         * @var bool $isForeignKey
537
         * @var bool $isAssociation
538
         * @var mixed $before
539
         * @var mixed $after
540
         * @var array $beforeValues
541
         * @var array $afterValues
542
         * @var string $column
543
         * @var array $columnDef
544
         * @var \Cake\ORM\Table $table
545
         * @var \Cake\ORM\Table $Columns
546
         */
547
548
        /**
549
         * Date inputs sometime represents string value in
550
         * entity. This converts value for comparison.
551
         */
552
        if ($this->config('convertDatetimeColumns') && !$isAssociation && isset($columnDef['type'])) {
553
            switch ($columnDef['type']) {
554
                case 'date':
555
                case 'datetime':
556
                case 'time':
557
                    $baseType = $table->schema()->baseColumnType($column);
558
                    if ($baseType && Type::map($baseType)) {
559
                        $driver = $table->connection()->driver();
560
                        $before = Type::build($baseType)->toPHP($before, $driver);
561
                        $after = Type::build($baseType)->toPHP($after, $driver);
562
                    }
563
                    break;
564
            }
565
        }
566
567
        /**
568
         * Converts foreign keys. This converts belongsTo ID columns to associated
569
         * entity. Then it takes display field for the table.
570
         */
571
        if ($isForeignKey && $this->config('convertForeignKeys')) {
572
            if ($this->config('exchangeForeignKey')) {
573
                unset($data['beforeValues'][$column]);
574
                unset($data['afterValues'][$column]);
575
                $column = $association->property();
576
                $data['column'] = $column;
577
                $this->_combinationColumns[] = $column;
578
            }
579
580
            $before = $association->findById($before)->first();
581
            // belongsTo association requires beforeValue to convert
582
            $this->_collectedBeforeValues[$association->property()] = $before ? $before->toArray() : [];
583
            $after = $association->findById($after)->first();
584
            $before = $this->convertAssociationChangeValue($before, $association, 'before');
585
            $after = $this->convertAssociationChangeValue($after, $association, 'after');
586
        }
587
588
        /**
589
         * Converts associations
590
         */
591
        $converter = $this->config('convertAssociations');
592
        if ($isAssociation && $converter) {
593
            /** 
594
             * If array was given, handles it as whitelist of associations
595
             */
596
            if (!is_array($converter) || is_callable($converter) || in_array($column, $converter)) {
597
                $before = $this->convertAssociationChangeValue($before, $association, 'before');
598
                $after = $this->convertAssociationChangeValue($after, $association, 'after');
599
            }
600
        }
601
602
        /**
603
         * Modifies event data
604
         */
605
        $data['before'] = $before;
606
        $data['after'] = $after;
607
        $data['beforeValues'][$column] = $before;
608
        $data['afterValues'][$column] = $after;
609
    }
610
611
    /**
612
     * Default converter for association values
613
     */
614
    public function convertAssociationChangeValue($value, $association, $kind)
615
    {
616
        if (!$value) {
617
            return is_array($value) ? null : $value;
618
        }
619
620
        $isMany = in_array($association->type(), [Association::MANY_TO_MANY, Association::ONE_TO_MANY]);
621
        $property = $association->property();
622
        $beforeValue = Hash::get($this->_collectedBeforeValues, $property);
623
624
        /**
625
         * Call actual converter. callable can be set with `convertAssociations`
626
         * option.
627
         */
628
        $converter = $this->config('convertAssociations');
629
        $callable = is_callable($converter) ? $converter : [$this, 'defaultConvertAssociation'];
630
        $arguments = [$property, $value, $kind, $association, $isMany, $beforeValue];
631
632
        return call_user_func_array($callable, $arguments);
633
    }
634
635
    /**
636
     * Default converter for association values.
637
     *
638
     * @param string $property association property name
639
     * @param mixed $value expects EntityInterface/EntityInterface[]
640
     * @param string $kind either 'before'/'after'
641
     * @param \Cake\ORM\Association $association association object for the value
642
     * @param boolean $isMany true => [hasMany, belongsToMany] false => [hasOne, belongsTo]
643
     * @param array $beforeValue association original values. indexed by association properties.
644
     * @return mixed converted value
645
     */
646
    public function defaultConvertAssociation($property, $value, $kind, $association, $isMany, $beforeValue)
647
    {
648
        $displayField = $association->displayField();
649
        if ($kind === 'before' && !$beforeValue) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $beforeValue of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
650
            return null;
651
        }
652
653
        if (!$value) {
654
            return null;
655
        }
656
657
        // hasMany, belongsToMany
658
        if ($isMany) {
659
            $values = $kind === 'before' ? $beforeValue : (array)$value;
660
            return implode(', ', collection($values)->extract($displayField)
661
                ->filter()
662
                ->toArray());
663
        // hasOne, belongsTo
664
        } else {
665
            if ($kind === 'before') {
666
                if ($beforeValue instanceof EntityInterface) {
667
                    return $beforeValue->get($displayField);
668
                }
669
670
                return Hash::get($beforeValue, $displayField);
671
            } else {
672
                if (!$value instanceof EntityInterface) {
673
                    return $value;
674
                }
675
                return $value->get($displayField);
676
            }
677
        }
678
    }
679
680
    /**
681
     * Default filter
682
     *
683
     * @param \Cake\Event\Event $event The event for callback
684
     * @param ArrayObject $data The event data. contains:
685
     *                          - entity
686
     *                          - isForeignKey
687
     *                          - isAssociation
688
     *                          - before
689
     *                          - after
690
     *                          - beforeValues
691
     *                          - afterValues
692
     *                          - column
693
     *                          - columnDef
694
     *                          - table
695
     *                          - Columns
696
     * @return bool column is changed or not
697
     */
698
    public function filterChanges(Event $event, ArrayObject $data)
699
    {
700
        extract((array)$data);
0 ignored issues
show
Bug introduced by
(array) $data cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
701
        /**
702
         * @var \Cake\ORM\Entity $entity
703
         * @var bool $isForeignKey
704
         * @var bool $isAssociation
705
         * @var mixed $before
706
         * @var mixed $after
707
         * @var array $beforeValues
708
         * @var array $afterValues
709
         * @var string $column
710
         * @var array $columnDef
711
         * @var \Cake\ORM\Table $table
712
         * @var \Cake\ORM\Table $Columns
713
         */
714
715
        /**
716
         * Filter e.g. null != ''
717
         */
718
        if ($this->config('equalComparison')) {
719
            return $before != $after;
720
        }
721
722
        /**
723
         * Filter foreign keys
724
         */
725
        if ($this->config('filterForeignKeys')) {
726
            return !$isForeignKey;
727
        }
728
729
        return true;
730
    }
731
732
    /**
733
     * Default save process
734
     *
735
     * @param \Cake\Event\Event $event The event for callback
736
     * @param \Cake\ORM\Table $Changelogs The table for parent
737
     * @param ArrayObject $data save data
738
     * @param ArrayObject $options save options
739
     * @return bool column is changed or not
740
     */
741
    public function saveChangelogRecords(Event $event, Table $Changelogs, ArrayObject $data, ArrayObject $options)
742
    {
743
        /**
744
         * Save changes to database
745
         */
746
        if ($this->config('autoSave')) {
747
            $changelog = $Changelogs->newEntity((array)$data);
748
            return $Changelogs->save($changelog, (array)$options);
749
        }
750
    }
751
752
}
753