Completed
Push — master ( 8f80f8...5db0fb )
by Quetzy
13:25
created

Audit::getMetadata()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 17
ccs 10
cts 10
cp 1
rs 9.6111
c 0
b 0
f 0
cc 5
nc 12
nop 3
crap 5
1
<?php
2
3
namespace OwenIt\Auditing;
4
5
use DateTimeInterface;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Relations\MorphTo;
8
use Illuminate\Support\Facades\Config;
9
use OwenIt\Auditing\Contracts\AttributeEncoder;
10
11
trait Audit
12
{
13
    /**
14
     * Audit data.
15
     *
16
     * @var array
17
     */
18
    protected $data = [];
19
20
    /**
21
     * The Audit attributes that belong to the metadata.
22
     *
23
     * @var array
24
     */
25
    protected $metadata = [];
26
27
    /**
28
     * The Auditable attributes that were modified.
29
     *
30
     * @var array
31
     */
32
    protected $modified = [];
33
34
    /**
35
     * {@inheritdoc}
36
     */
37 36
    public function auditable(): MorphTo
38
    {
39 36
        return $this->morphTo();
0 ignored issues
show
Bug introduced by
It seems like morphTo() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

39
        return $this->/** @scrutinizer ignore-call */ morphTo();
Loading history...
40
    }
41
42
    /**
43
     * {@inheritdoc}
44
     */
45 48
    public function user(): MorphTo
46
    {
47 48
        return $this->morphTo();
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 144
    public function getConnectionName()
54
    {
55 144
        return Config::get('audit.drivers.database.connection');
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61 144
    public function getTable(): string
62
    {
63 144
        return Config::get('audit.drivers.database.table', parent::getTable());
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69 48
    public function resolveData(): array
70
    {
71 48
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
72
73
        // Metadata
74 48
        $this->data = [
75 48
            'audit_id'         => $this->id,
76 48
            'audit_event'      => $this->event,
77 48
            'audit_url'        => $this->url,
78 48
            'audit_ip_address' => $this->ip_address,
79 48
            'audit_user_agent' => $this->user_agent,
80 48
            'audit_tags'       => $this->tags,
81 48
            'audit_created_at' => $this->serializeDate($this->created_at),
0 ignored issues
show
Bug introduced by
It seems like serializeDate() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

81
            'audit_created_at' => $this->/** @scrutinizer ignore-call */ serializeDate($this->created_at),
Loading history...
82 48
            'audit_updated_at' => $this->serializeDate($this->updated_at),
83 48
            'user_id'          => $this->getAttribute($morphPrefix.'_id'),
0 ignored issues
show
Bug introduced by
It seems like getAttribute() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

83
            'user_id'          => $this->/** @scrutinizer ignore-call */ getAttribute($morphPrefix.'_id'),
Loading history...
84 48
            'user_type'        => $this->getAttribute($morphPrefix.'_type'),
85
        ];
86
87 48
        if ($this->user) {
88 33
            foreach ($this->user->getArrayableAttributes() as $attribute => $value) {
89 33
                $this->data['user_'.$attribute] = $value;
90
            }
91
        }
92
93 48
        $this->metadata = array_keys($this->data);
94
95
        // Modified Auditable attributes
96 48
        foreach ($this->new_values as $key => $value) {
97 39
            $this->data['new_'.$key] = $value;
98
        }
99
100 48
        foreach ($this->old_values as $key => $value) {
101 9
            $this->data['old_'.$key] = $value;
102
        }
103
104 48
        $this->modified = array_diff_key(array_keys($this->data), $this->metadata);
105
106 48
        return $this->data;
107
    }
108
109
    /**
110
     * Get the formatted value of an Eloquent model.
111
     *
112
     * @param Model  $model
113
     * @param string $key
114
     * @param mixed  $value
115
     *
116
     * @return mixed
117
     */
118 30
    protected function getFormattedValue(Model $model, string $key, $value)
119
    {
120
        // Apply defined get mutator
121 30
        if ($model->hasGetMutator($key)) {
122 27
            return $model->mutateAttribute($key, $value);
123
        }
124
125
        // Cast to native PHP type
126 30
        if ($model->hasCast($key)) {
127 18
            return $model->castAttribute($key, $value);
128
        }
129
130
        // Honour DateTime attribute
131 30
        if ($value !== null && in_array($key, $model->getDates(), true)) {
132 15
            return $model->asDateTime($value);
133
        }
134
135 30
        return $value;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141 36
    public function getDataValue(string $key)
142
    {
143 36
        if (!array_key_exists($key, $this->data)) {
144 3
            return;
145
        }
146
147 36
        $value = $this->data[$key];
148
149
        // User value
150 36
        if ($this->user && starts_with($key, 'user_')) {
151 9
            return $this->getFormattedValue($this->user, substr($key, 5), $value);
152
        }
153
154
        // Auditable value
155 36
        if ($this->auditable && starts_with($key, ['new_', 'old_'])) {
156 24
            $attribute = substr($key, 4);
157
158 24
            return $this->getFormattedValue(
159 24
                $this->auditable,
160 24
                $attribute,
161 24
                $this->decodeAttributeValue($this->auditable, $attribute, $value)
162
            );
163
        }
164
165 12
        return $value;
166
    }
167
168
    /**
169
     * Decode attribute value.
170
     *
171
     * @param Contracts\Auditable $auditable
172
     * @param string              $attribute
173
     * @param mixed               $value
174
     *
175
     * @return mixed
176
     */
177 24
    protected function decodeAttributeValue(Contracts\Auditable $auditable, string $attribute, $value)
178
    {
179 24
        $attributeModifiers = $auditable->getAttributeModifiers();
180
181 24
        if (!array_key_exists($attribute, $attributeModifiers)) {
182 24
            return $value;
183
        }
184
185 3
        $attributeDecoder = $attributeModifiers[$attribute];
186
187 3
        if (is_subclass_of($attributeDecoder, AttributeEncoder::class)) {
188 3
            return call_user_func([$attributeDecoder, 'decode'], $value);
189
        }
190
191 3
        return $value;
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 12
    public function getMetadata(bool $json = false, int $options = 0, int $depth = 512)
198
    {
199 12
        if (empty($this->data)) {
200 12
            $this->resolveData();
201
        }
202
203 12
        $metadata = [];
204
205 12
        foreach ($this->metadata as $key) {
206 12
            $value = $this->getDataValue($key);
207
208 12
            $metadata[$key] = $value instanceof DateTimeInterface
209 6
                ? $this->serializeDate($value)
210 12
                : $value;
211
        }
212
213 12
        return $json ? json_encode($metadata, $options, $depth) : $metadata;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 27
    public function getModified(bool $json = false, int $options = 0, int $depth = 512)
220
    {
221 27
        if (empty($this->data)) {
222 27
            $this->resolveData();
223
        }
224
225 27
        $modified = [];
226
227 27
        foreach ($this->modified as $key) {
228 21
            $attribute = substr($key, 4);
229 21
            $state = substr($key, 0, 3);
230
231 21
            $value = $this->getDataValue($key);
232
233 21
            $modified[$attribute][$state] = $value instanceof DateTimeInterface
234 6
                ? $this->serializeDate($value)
235 21
                : $value;
236
        }
237
238 27
        return $json ? json_encode($modified, $options, $depth) : $modified;
239
    }
240
241
    /**
242
     * Get the Audit tags as an array.
243
     *
244
     * @return array
245
     */
246 6
    public function getTags(): array
247
    {
248 6
        return preg_split('/,/', $this->tags, null, PREG_SPLIT_NO_EMPTY);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_split('/,/',...ng\PREG_SPLIT_NO_EMPTY) could return the type false which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
249
    }
250
}
251