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
|
|
|
'collectChangesOnBeforeSave' => 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) |
|
|
|
|
100
|
|
|
{ |
101
|
|
|
if ($this->config('collectChangesOnBeforeSave')) { |
|
|
|
|
102
|
|
|
$this->collectChanges($entity, $options); |
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
public function collectChanges(EntityInterface $entity, ArrayObject $options = null) |
107
|
|
|
{ |
108
|
|
|
$Changelogs = $this->getChangelogTable(); |
|
|
|
|
109
|
|
|
$Columns = $this->getColumnTable(); |
110
|
|
|
$table = $this->_table; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Be sure whether log new entity or not. |
114
|
|
|
*/ |
115
|
|
|
if (!$this->config('logIsNew') && $entity->isNew()) { |
|
|
|
|
116
|
|
|
return false; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Extract column names from original values |
121
|
|
|
*/ |
122
|
|
|
$columns = array_keys($entity->getOriginalValues()); |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Extract dirty columns. |
126
|
|
|
* Exchange columns to actually dirty ones. |
127
|
|
|
*/ |
128
|
|
|
$afterValues = $entity->extract($columns, $isDirty = true); |
129
|
|
|
$columns = array_keys($afterValues); |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Adds extra columns when combinations was given. |
133
|
|
|
*/ |
134
|
|
|
$this->_combinationColumns = []; |
135
|
|
|
if ($combinations = $this->config('combinations')) { |
|
|
|
|
136
|
|
|
foreach ($combinations as $name => $settings) { |
137
|
|
|
$settings = $this->_normalizeCombinationSettings($settings); |
138
|
|
|
$this->_combinationColumns = array_merge($this->_combinationColumns, $settings['columns']); |
139
|
|
|
} |
140
|
|
|
$this->_combinationColumns = array_values(array_unique($this->_combinationColumns)); |
141
|
|
|
|
142
|
|
|
$columns = array_values(array_unique(array_merge($columns, $this->_combinationColumns))); |
143
|
|
|
$afterValues = $entity->extract($columns); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Extract original value from decided columns. |
148
|
|
|
*/ |
149
|
|
|
$beforeValues = $entity->extractOriginal($columns); |
|
|
|
|
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Filters ignored columns |
153
|
|
|
*/ |
154
|
|
|
$columns = array_diff($columns, $this->config('ignoreColumns')); |
|
|
|
|
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Exception, before counts should equal to after counts |
158
|
|
|
*/ |
159
|
|
|
if (count($beforeValues) !== count($afterValues)) { |
160
|
|
|
return false; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Filters changes |
165
|
|
|
*/ |
166
|
|
|
$changes = []; |
167
|
|
|
$associations = $this->_associationsIndexedByProperty(); |
168
|
|
|
$foreignKeys = $this->_associationsForForeignKey(); |
169
|
|
|
foreach ($columns as $column) { |
170
|
|
|
/** |
171
|
|
|
* Prepare values for events |
172
|
|
|
*/ |
173
|
|
|
$before = $beforeValues[$column]; |
174
|
|
|
$after = $afterValues[$column]; |
175
|
|
|
|
176
|
|
|
$isAssociation = array_key_exists($column, $associations); |
177
|
|
|
$isForeignKey = array_key_exists($column, $foreignKeys); |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Prepare association/column info |
181
|
|
|
*/ |
182
|
|
|
if ($isAssociation) { |
183
|
|
|
$columnDef = null; |
184
|
|
|
$association = $associations[$column]; |
185
|
|
|
} else { |
186
|
|
|
$columnDef = $table->schema()->column($column); |
|
|
|
|
187
|
|
|
$association = null; |
188
|
|
|
if ($isForeignKey) { |
189
|
|
|
$association = $foreignKeys[$column]; |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Event data. These variables can be changed via registered events. |
195
|
|
|
*/ |
196
|
|
|
$eventData = new ArrayObject(compact([ |
197
|
|
|
'entity', |
198
|
|
|
'isForeignKey', |
199
|
|
|
'isAssociation', |
200
|
|
|
'before', |
201
|
|
|
'after', |
202
|
|
|
'beforeValues', |
203
|
|
|
'afterValues', |
204
|
|
|
'column', |
205
|
|
|
'columnDef', |
206
|
|
|
'association', |
207
|
|
|
'table', |
208
|
|
|
'Columns' |
209
|
|
|
])); |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Dispatches convert event |
213
|
|
|
*/ |
214
|
|
|
$event = $table->dispatchEvent('Changelog.convertValues', [$eventData]); |
|
|
|
|
215
|
|
|
extract((array)$eventData); |
|
|
|
|
216
|
|
|
|
217
|
|
|
if (!$this->_combinationColumns || !in_array($column, $this->_combinationColumns)) { |
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Dispatches filter event |
220
|
|
|
*/ |
221
|
|
|
$event = $table->dispatchEvent('Changelog.filterChanges', [$eventData]); |
222
|
|
|
if (!$event->result) { |
223
|
|
|
continue; |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Determine changes from result |
229
|
|
|
*/ |
230
|
|
|
extract((array)$eventData); |
|
|
|
|
231
|
|
|
$changes[] = [ |
232
|
|
|
'column' => $column, |
233
|
|
|
'before' => $before, |
234
|
|
|
'after' => $after, |
235
|
|
|
]; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Make combinations |
240
|
|
|
*/ |
241
|
|
|
if ($combinations = $this->config('combinations')) { |
|
|
|
|
242
|
|
|
$changes = $this->makeChangelogCombinations($entity, $changes, $combinations); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
return $this->_changes = $changes; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
public function makeChangelogCombinations(EntityInterface $entity, array $changes, array $combinations) |
249
|
|
|
{ |
250
|
|
|
$indexedByColumn = collection($changes)->indexBy('column')->toArray(); |
251
|
|
|
$removeKeys = []; |
252
|
|
|
$foreignKeys = $this->_associationsForForeignKey(); |
253
|
|
|
foreach ($combinations as $name => $settings) { |
254
|
|
|
$settings = $this->_normalizeCombinationSettings($settings); |
255
|
|
|
|
256
|
|
|
$values = []; |
257
|
|
|
foreach ($settings['columns'] as $column) { |
258
|
|
|
$removeKeys[] = $column; |
259
|
|
|
|
260
|
|
|
if ($association = Hash::get($foreignKeys, $column)) { |
261
|
|
|
$property = $association->property(); |
262
|
|
|
if (isset($indexedByColumn[$property])) { |
263
|
|
|
$values['before'][$column] = $indexedByColumn[$property]['before']; |
264
|
|
|
$values['after'][$column] = $indexedByColumn[$property]['after']; |
265
|
|
|
$removeKeys[] = $property; |
266
|
|
|
} elseif (isset($indexedByColumn[$column])) { |
267
|
|
|
$values['before'][$column] = $indexedByColumn[$column]['before']; |
268
|
|
|
$values['after'][$column] = $indexedByColumn[$column]['after']; |
269
|
|
|
} else { |
270
|
|
|
$values['before'][$column] = $entity->get($column); |
271
|
|
|
$values['after'][$column] = $entity->get($column); |
272
|
|
|
} |
273
|
|
|
} elseif (isset($indexedByColumn[$column])) { |
274
|
|
|
$values['before'][$column] = $indexedByColumn[$column]['before']; |
275
|
|
|
$values['after'][$column] = $indexedByColumn[$column]['after']; |
276
|
|
|
} else { |
277
|
|
|
$values['before'][$column] = $entity->get($column); |
278
|
|
|
$values['after'][$column] = $entity->get($column); |
279
|
|
|
} |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
if (isset($settings['convert'])) { |
283
|
|
|
$convert = $settings['convert']; |
284
|
|
|
$converted = $convert($name, $values['before'], $values['after']); |
285
|
|
|
} else { |
286
|
|
|
$converted = [ |
287
|
|
|
'column' => $name, |
288
|
|
|
'before' => implode(' ', array_filter($values['before'])), |
289
|
|
|
'after' => implode(' ', array_filter($values['after'])), |
290
|
|
|
]; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Filters converted value not equal to. also remove keys from indexedColumn |
295
|
|
|
* to prevent changes remaining. |
296
|
|
|
*/ |
297
|
|
|
if ($converted['before'] == $converted['after']) { |
298
|
|
|
unset($combinations[$name]); |
299
|
|
|
continue; |
300
|
|
|
} |
301
|
|
|
$indexedByColumn[$name] = $converted; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
$removeKeys = array_diff($removeKeys, array_keys($combinations)); |
305
|
|
|
$indexedByColumn = array_diff_key($indexedByColumn, array_flip($removeKeys)); |
306
|
|
|
return array_values($indexedByColumn); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
protected function _normalizeCombinationSettings($settings) |
310
|
|
|
{ |
311
|
|
|
if (!is_array($settings)) { |
312
|
|
|
throw new UnexpectedValueException(__d('changelog', 'Changelog: `combinations` option should be array')); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* If numric keys e.g. ['first_name', 'last_name'] given, Handles it |
317
|
|
|
* as a list of columns. |
318
|
|
|
*/ |
319
|
|
|
if (Hash::numeric(array_keys($settings))) { |
320
|
|
|
$settings = ['columns' => $settings]; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
if (!isset($settings['columns']) || !is_array($settings['columns'])) { |
324
|
|
|
throw new UnexpectedValueException(__d('changelog', 'Changelog: `combinations` option should have `columns` key and value as array of columns')); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
return $settings; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* afterSave callback. |
332
|
|
|
* This logs entities when `onAfterSave` option was turned on. |
333
|
|
|
* |
334
|
|
|
* {@inheritdoc} |
335
|
|
|
*/ |
336
|
|
|
public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options) |
337
|
|
|
{ |
338
|
|
|
if ($this->config('saveChangelogOnAfterSave')) { |
|
|
|
|
339
|
|
|
$this->saveChangelog($entity, $this->_changes); |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Saves changelogs for entity. |
345
|
|
|
* |
346
|
|
|
* @param \Cake\Datasource\EntityInterface $entity The entity object to log changes. |
347
|
|
|
* @return \Cake\Datasource\EntityInterface|bool Entity object when logged otherwise `false`. |
348
|
|
|
*/ |
349
|
|
|
public function saveChangelog(EntityInterface $entity, $changes = []) |
350
|
|
|
{ |
351
|
|
|
/** |
352
|
|
|
* Be sure whether change was done or not |
353
|
|
|
*/ |
354
|
|
|
if (!$this->config('logEmptyChanges') && empty($changes)) { |
|
|
|
|
355
|
|
|
return false; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Saves actually |
360
|
|
|
*/ |
361
|
|
|
$data = new ArrayObject([ |
362
|
|
|
'model' => $this->_table->alias(), |
|
|
|
|
363
|
|
|
'foreign_key' => $entity->get($this->_table->primaryKey()), |
|
|
|
|
364
|
|
|
'is_new' => $entity->isNew(), |
365
|
|
|
'changelog_columns' => $changes, |
366
|
|
|
]); |
367
|
|
|
$options = new ArrayObject([ |
368
|
|
|
'associated' => 'ChangelogColumns', |
369
|
|
|
'atomic' => false |
370
|
|
|
]); |
371
|
|
|
|
372
|
|
|
$Changelogs = $this->getChangelogTable(); |
373
|
|
|
return $this->_table->dispatchEvent('Changelog.saveChangelogRecords', compact('Changelogs', 'data', 'options'))->result; |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* Helper method to get table associations array |
378
|
|
|
* indexed by these properties. |
379
|
|
|
* |
380
|
|
|
* @return \Cake\ORM\Association[] |
381
|
|
|
*/ |
382
|
|
|
protected function _associationsIndexedByProperty() |
383
|
|
|
{ |
384
|
|
|
return collection($this->_table->associations()) |
385
|
|
|
->combine(function ($association) { |
386
|
|
|
return $association->property(); |
387
|
|
|
}, function ($association) { |
388
|
|
|
return $association; |
389
|
|
|
})->toArray(); |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Helper method to get associations array that table has |
394
|
|
|
* foreign key (means BelongsTo) indexed by foreign key. |
395
|
|
|
* |
396
|
|
|
* @return \Cake\ORM\Association[] |
397
|
|
|
*/ |
398
|
|
|
protected function _associationsForForeignKey() |
399
|
|
|
{ |
400
|
|
|
return collection($this->_table->associations()) |
401
|
|
|
->filter(function ($association) { |
402
|
|
|
return $association instanceof BelongsTo; |
403
|
|
|
})->combine(function ($association) { |
404
|
|
|
return $association->foreignKey(); |
405
|
|
|
}, function ($association) { |
406
|
|
|
return $association; |
407
|
|
|
})->toArray(); |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Returns changelogs table |
412
|
|
|
* |
413
|
|
|
* @return \Cake\ORM\Table |
414
|
|
|
*/ |
415
|
|
|
public function getChangelogTable() |
416
|
|
|
{ |
417
|
|
|
return $this->tableLocator()->get($this->config('changelogTable')); |
|
|
|
|
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Returns changelogs table |
422
|
|
|
* |
423
|
|
|
* @return \Cake\ORM\Table |
424
|
|
|
*/ |
425
|
|
|
public function getColumnTable() |
426
|
|
|
{ |
427
|
|
|
return $this->tableLocator()->get($this->config('columnTable')); |
|
|
|
|
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* Define additional events for filter |
432
|
|
|
* |
433
|
|
|
* {@inheritdoc} |
434
|
|
|
*/ |
435
|
|
|
public function implementedEvents() |
436
|
|
|
{ |
437
|
|
|
return parent::implementedEvents() + [ |
438
|
|
|
'Changelog.convertValues' => 'convertChangelogValues', |
439
|
|
|
'Changelog.filterChanges' => 'filterChanges', |
440
|
|
|
'Changelog.saveChangelogRecords' => 'saveChangelogRecords', |
441
|
|
|
]; |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* Default convert process |
446
|
|
|
* |
447
|
|
|
* @param \Cake\Event\Event $event The event for callback |
448
|
|
|
* @param ArrayObject $data The event data. contains: |
449
|
|
|
* - entity |
450
|
|
|
* - isForeignKey |
451
|
|
|
* - isAssociation |
452
|
|
|
* - before |
453
|
|
|
* - after |
454
|
|
|
* - beforeValues |
455
|
|
|
* - afterValues |
456
|
|
|
* - column |
457
|
|
|
* - columnDef |
458
|
|
|
* - table |
459
|
|
|
* - Columns |
460
|
|
|
* @return array couple of $before, $after |
461
|
|
|
*/ |
462
|
|
|
public function convertChangelogValues(Event $event, ArrayObject $data) |
463
|
|
|
{ |
464
|
|
|
extract((array)$data); |
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* @var \Cake\ORM\Entity $entity |
467
|
|
|
* @var bool $isForeignKey |
468
|
|
|
* @var bool $isAssociation |
469
|
|
|
* @var mixed $before |
470
|
|
|
* @var mixed $after |
471
|
|
|
* @var array $beforeValues |
472
|
|
|
* @var array $afterValues |
473
|
|
|
* @var string $column |
474
|
|
|
* @var array $columnDef |
475
|
|
|
* @var \Cake\ORM\Table $table |
476
|
|
|
* @var \Cake\ORM\Table $Columns |
477
|
|
|
*/ |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* Date inputs sometime represents string value in |
481
|
|
|
* entity. This converts value for comparison. |
482
|
|
|
*/ |
483
|
|
|
if ($this->config('convertDatetimeColumns') && !$isAssociation && isset($columnDef['type'])) { |
|
|
|
|
484
|
|
|
switch ($columnDef['type']) { |
485
|
|
|
case 'date': |
486
|
|
|
case 'datetime': |
487
|
|
|
case 'time': |
488
|
|
|
// For Postgres |
489
|
|
|
case 'timestamp': |
490
|
|
|
$baseType = $table->schema()->baseColumnType($column); |
491
|
|
|
if ($baseType && Type::map($baseType)) { |
492
|
|
|
$driver = $table->connection()->driver(); |
493
|
|
|
$before = Type::build($baseType)->toPHP($before, $driver); |
494
|
|
|
$after = Type::build($baseType)->toPHP($after, $driver); |
495
|
|
|
} |
496
|
|
|
break; |
497
|
|
|
} |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* Converts foreign keys. This converts belongsTo ID columns to associated |
502
|
|
|
* entity. Then it takes display field for the table. |
503
|
|
|
*/ |
504
|
|
|
if ($isForeignKey && $this->config('convertForeignKeys')) { |
|
|
|
|
505
|
|
|
if ($this->config('exchangeForeignKey')) { |
|
|
|
|
506
|
|
|
unset($data['beforeValues'][$column]); |
507
|
|
|
unset($data['afterValues'][$column]); |
508
|
|
|
$column = $association->property(); |
509
|
|
|
$data['column'] = $column; |
510
|
|
|
$this->_combinationColumns[] = $column; |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
$before = $association->findById($before)->first(); |
514
|
|
|
$after = $association->findById($after)->first(); |
515
|
|
|
$before = $this->convertAssociationChangeValue($before, $association, 'before'); |
516
|
|
|
$after = $this->convertAssociationChangeValue($after, $association, 'after'); |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
/** |
520
|
|
|
* Converts associations |
521
|
|
|
*/ |
522
|
|
|
$converter = $this->config('convertAssociations'); |
|
|
|
|
523
|
|
|
if ($isAssociation && $converter) { |
524
|
|
|
/** |
525
|
|
|
* If array was given, handles it as whitelist of associations |
526
|
|
|
*/ |
527
|
|
|
if (!is_array($converter) || is_callable($converter) || in_array($column, $converter)) { |
528
|
|
|
$before = $this->convertAssociationChangeValue($before, $association, 'before'); |
529
|
|
|
$after = $this->convertAssociationChangeValue($after, $association, 'after'); |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
/** |
534
|
|
|
* Modifies event data |
535
|
|
|
*/ |
536
|
|
|
$data['before'] = $before; |
537
|
|
|
$data['after'] = $after; |
538
|
|
|
$data['beforeValues'][$column] = $before; |
539
|
|
|
$data['afterValues'][$column] = $after; |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Default converter for association values |
544
|
|
|
*/ |
545
|
|
|
public function convertAssociationChangeValue($value, $association, $kind) |
546
|
|
|
{ |
547
|
|
|
if (!$value) { |
548
|
|
|
return is_array($value) ? null : $value; |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
$isMany = in_array($association->type(), [Association::MANY_TO_MANY, Association::ONE_TO_MANY]); |
552
|
|
|
$property = $association->property(); |
553
|
|
|
|
554
|
|
|
/** |
555
|
|
|
* Call actual converter. callable can be set with `convertAssociations` |
556
|
|
|
* option. |
557
|
|
|
*/ |
558
|
|
|
$converter = $this->config('convertAssociations'); |
|
|
|
|
559
|
|
|
$callable = is_callable($converter) ? $converter : [$this, 'defaultConvertAssociation']; |
560
|
|
|
$arguments = [$property, $value, $kind, $association, $isMany]; |
561
|
|
|
|
562
|
|
|
return call_user_func_array($callable, $arguments); |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
/** |
566
|
|
|
* Default converter for association values. |
567
|
|
|
* |
568
|
|
|
* @param string $property association property name |
569
|
|
|
* @param mixed $value expects EntityInterface/EntityInterface[] |
570
|
|
|
* @param string $kind either 'before'/'after' |
571
|
|
|
* @param \Cake\ORM\Association $association association object for the value |
572
|
|
|
* @param boolean $isMany true => [hasMany, belongsToMany] false => [hasOne, belongsTo] |
573
|
|
|
* @param array $beforeValue association original values. indexed by association properties. |
|
|
|
|
574
|
|
|
* @return mixed converted value |
575
|
|
|
*/ |
576
|
|
|
public function defaultConvertAssociation($property, $value, $kind, $association, $isMany) |
577
|
|
|
{ |
578
|
|
|
if (!$value) { |
579
|
|
|
return null; |
580
|
|
|
} |
581
|
|
|
$displayField = $association->displayField(); |
582
|
|
|
|
583
|
|
|
// hasMany, belongsToMany |
584
|
|
|
if ($isMany) { |
585
|
|
|
$values = (array)$value; |
586
|
|
|
return implode(', ', collection($values) |
587
|
|
|
->sortBy($displayField, SORT_ASC, SORT_NATURAL) |
588
|
|
|
->extract($displayField) |
589
|
|
|
->filter() |
590
|
|
|
->toArray()); |
591
|
|
|
// hasOne, belongsTo |
592
|
|
|
} else { |
593
|
|
|
if (!$value instanceof EntityInterface) { |
594
|
|
|
return $value; |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
if ($kind === 'before') { |
598
|
|
|
return $value->getOriginal($displayField); |
599
|
|
|
} else { |
600
|
|
|
return $value->get($displayField); |
601
|
|
|
} |
602
|
|
|
} |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* Default filter |
607
|
|
|
* |
608
|
|
|
* @param \Cake\Event\Event $event The event for callback |
609
|
|
|
* @param ArrayObject $data The event data. contains: |
610
|
|
|
* - entity |
611
|
|
|
* - isForeignKey |
612
|
|
|
* - isAssociation |
613
|
|
|
* - before |
614
|
|
|
* - after |
615
|
|
|
* - beforeValues |
616
|
|
|
* - afterValues |
617
|
|
|
* - column |
618
|
|
|
* - columnDef |
619
|
|
|
* - table |
620
|
|
|
* - Columns |
621
|
|
|
* @return bool column is changed or not |
622
|
|
|
*/ |
623
|
|
|
public function filterChanges(Event $event, ArrayObject $data) |
624
|
|
|
{ |
625
|
|
|
extract((array)$data); |
|
|
|
|
626
|
|
|
/** |
627
|
|
|
* @var \Cake\ORM\Entity $entity |
628
|
|
|
* @var bool $isForeignKey |
629
|
|
|
* @var bool $isAssociation |
630
|
|
|
* @var mixed $before |
631
|
|
|
* @var mixed $after |
632
|
|
|
* @var array $beforeValues |
633
|
|
|
* @var array $afterValues |
634
|
|
|
* @var string $column |
635
|
|
|
* @var array $columnDef |
636
|
|
|
* @var \Cake\ORM\Table $table |
637
|
|
|
* @var \Cake\ORM\Table $Columns |
638
|
|
|
*/ |
639
|
|
|
|
640
|
|
|
/** |
641
|
|
|
* Filter e.g. null != '' |
642
|
|
|
*/ |
643
|
|
|
if ($this->config('equalComparison')) { |
|
|
|
|
644
|
|
|
return $before != $after; |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
/** |
648
|
|
|
* Filter foreign keys |
649
|
|
|
*/ |
650
|
|
|
if ($this->config('filterForeignKeys')) { |
|
|
|
|
651
|
|
|
return !$isForeignKey; |
652
|
|
|
} |
653
|
|
|
|
654
|
|
|
return true; |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
/** |
658
|
|
|
* Default save process |
659
|
|
|
* |
660
|
|
|
* @param \Cake\Event\Event $event The event for callback |
661
|
|
|
* @param \Cake\ORM\Table $Changelogs The table for parent |
662
|
|
|
* @param ArrayObject $data save data |
663
|
|
|
* @param ArrayObject $options save options |
664
|
|
|
* @return bool column is changed or not |
665
|
|
|
*/ |
666
|
|
|
public function saveChangelogRecords(Event $event, Table $Changelogs, ArrayObject $data, ArrayObject $options) |
667
|
|
|
{ |
668
|
|
|
/** |
669
|
|
|
* Save changes to database |
670
|
|
|
*/ |
671
|
|
|
if ($this->config('autoSave')) { |
|
|
|
|
672
|
|
|
$changelog = $Changelogs->newEntity((array)$data); |
673
|
|
|
return $Changelogs->save($changelog, (array)$options); |
674
|
|
|
} |
675
|
|
|
} |
676
|
|
|
|
677
|
|
|
} |
678
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.