1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Spatie\Activitylog\Traits; |
4
|
|
|
|
5
|
|
|
use Illuminate\Database\Eloquent\Model; |
6
|
|
|
use Spatie\Activitylog\Exceptions\CouldNotLogChanges; |
7
|
|
|
|
8
|
|
|
trait DetectsChanges |
9
|
|
|
{ |
10
|
|
|
protected $oldAttributes = []; |
11
|
|
|
|
12
|
|
|
protected static function bootDetectsChanges() |
13
|
|
|
{ |
14
|
|
|
if (static::eventsToBeRecorded()->contains('updated')) { |
15
|
|
|
static::updating(function (Model $model) { |
16
|
|
|
|
17
|
|
|
//temporary hold the original attributes on the model |
18
|
|
|
//as we'll need these in the updating event |
19
|
|
|
$oldValues = $model->replicate()->setRawAttributes($model->getOriginal()); |
20
|
|
|
|
21
|
|
|
$model->oldAttributes = static::logChanges($oldValues); |
|
|
|
|
22
|
|
|
}); |
23
|
|
|
} |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
public function attributesToBeLogged(): array |
27
|
|
|
{ |
28
|
|
|
$attributes = []; |
29
|
|
|
|
30
|
|
|
if (isset(static::$logFillable) && static::$logFillable) { |
31
|
|
|
$attributes = array_merge($attributes, $this->getFillable()); |
|
|
|
|
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
if ($this->shouldlogUnguarded()) { |
35
|
|
|
$attributes = array_merge($attributes, array_diff(array_keys($this->getAttributes()), $this->getGuarded())); |
|
|
|
|
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
if (isset(static::$logAttributes) && is_array(static::$logAttributes)) { |
39
|
|
|
$attributes = array_merge($attributes, array_diff(static::$logAttributes, ['*'])); |
40
|
|
|
|
41
|
|
|
if (in_array('*', static::$logAttributes)) { |
42
|
|
|
$attributes = array_merge($attributes, array_keys($this->getAttributes())); |
|
|
|
|
43
|
|
|
} |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
if (isset(static::$logAttributesToIgnore) && is_array(static::$logAttributesToIgnore)) { |
47
|
|
|
$attributes = array_diff($attributes, static::$logAttributesToIgnore); |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
return $attributes; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function shouldlogOnlyDirty(): bool |
54
|
|
|
{ |
55
|
|
|
if (! isset(static::$logOnlyDirty)) { |
56
|
|
|
return false; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return static::$logOnlyDirty; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
public function shouldlogUnguarded(): bool |
63
|
|
|
{ |
64
|
|
|
if (! isset(static::$logUnguarded)) { |
65
|
|
|
return false; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
if (! static::$logUnguarded) { |
69
|
|
|
return false; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
if (in_array('*', $this->getGuarded())) { |
|
|
|
|
73
|
|
|
return false; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
return true; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
public function attributeValuesToBeLogged(string $processingEvent): array |
80
|
|
|
{ |
81
|
|
|
if (! count($this->attributesToBeLogged())) { |
82
|
|
|
return []; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
$properties['attributes'] = static::logChanges( |
|
|
|
|
86
|
|
|
$this->exists |
|
|
|
|
87
|
|
|
? $this->fresh() ?? $this |
|
|
|
|
88
|
|
|
: $this |
89
|
|
|
); |
90
|
|
|
|
91
|
|
|
if (static::eventsToBeRecorded()->contains('updated') && $processingEvent == 'updated') { |
92
|
|
|
$nullProperties = array_fill_keys(array_keys($properties['attributes']), null); |
93
|
|
|
|
94
|
|
|
$properties['old'] = array_merge($nullProperties, $this->oldAttributes); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
if ($this->shouldlogOnlyDirty() && isset($properties['old'])) { |
98
|
|
|
$properties['attributes'] = array_udiff_assoc( |
99
|
|
|
$properties['attributes'], |
100
|
|
|
$properties['old'], |
101
|
|
|
function ($new, $old) { |
102
|
|
|
return $new <=> $old; |
103
|
|
|
} |
104
|
|
|
); |
105
|
|
|
$properties['old'] = collect($properties['old']) |
106
|
|
|
->only(array_keys($properties['attributes'])) |
107
|
|
|
->all(); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
return $properties; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
public static function logChanges(Model $model): array |
114
|
|
|
{ |
115
|
|
|
$changes = []; |
116
|
|
|
foreach ($model->attributesToBeLogged() as $attribute) { |
117
|
|
|
if (str_contains($attribute, '.')) { |
118
|
|
|
$changes += self::getRelatedModelAttributeValue($model, $attribute); |
119
|
|
|
} else { |
120
|
|
|
$changes += collect($model)->only($attribute)->toArray(); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
return $changes; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
protected static function getRelatedModelAttributeValue(Model $model, string $attribute): array |
128
|
|
|
{ |
129
|
|
|
if (substr_count($attribute, '.') > 1) { |
130
|
|
|
throw CouldNotLogChanges::invalidAttribute($attribute); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
list($relatedModelName, $relatedAttribute) = explode('.', $attribute); |
134
|
|
|
|
135
|
|
|
$relatedModel = $model->$relatedModelName ?? $model->$relatedModelName(); |
136
|
|
|
|
137
|
|
|
return ["{$relatedModelName}.{$relatedAttribute}" => $relatedModel->$relatedAttribute ?? null]; |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.