Test Failed
Push — master ( af71f9...0ec276 )
by
unknown
08:59
created

Audit::getDataValue()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 3
Bugs 0 Features 2
Metric Value
cc 6
eloc 12
c 3
b 0
f 2
nc 4
nop 1
dl 0
loc 25
ccs 0
cts 14
cp 0
crap 42
rs 9.2222
1
<?php
2
3
namespace OwenIt\Auditing;
4
5
use DateTimeInterface;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Support\Carbon;
8
use Illuminate\Support\Facades\Config;
9
use Illuminate\Support\Facades\Date;
10
use Illuminate\Support\Str;
11
use InvalidArgumentException;
12
use OwenIt\Auditing\Contracts\AttributeEncoder;
13
14
trait Audit
15
{
16
    /**
17
     * Audit data.
18
     *
19
     * @var array
20
     */
21
    protected $data = [];
22
23
    /**
24
     * The Audit attributes that belong to the metadata.
25
     *
26
     * @var array
27
     */
28
    protected $metadata = [];
29
30
    /**
31
     * The Auditable attributes that were modified.
32
     *
33
     * @var array
34
     */
35
    protected $modified = [];
36
37
    /**
38
     * {@inheritdoc}
39
     */
40
    public function auditable()
41
    {
42
        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

42
        return $this->/** @scrutinizer ignore-call */ morphTo();
Loading history...
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function user()
49
    {
50
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
51
52
        return $this->morphTo(__FUNCTION__, $morphPrefix . '_type', $morphPrefix . '_id');
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58 17
    public function getConnectionName()
59
    {
60 17
        return Config::get('audit.drivers.database.connection');
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 17
    public function getTable(): string
67
    {
68 17
        return Config::get('audit.drivers.database.table', parent::getTable());
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function resolveData(): array
75
    {
76
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
77
78
        // Metadata
79
        $this->data = [
80
            'audit_id'         => $this->getKey(),
0 ignored issues
show
Bug introduced by
It seems like getKey() 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

80
            'audit_id'         => $this->/** @scrutinizer ignore-call */ getKey(),
Loading history...
81
            'audit_event'      => $this->event,
82
            'audit_tags'       => $this->tags,
83
            'audit_created_at' => $this->serializeDate($this->{$this->getCreatedAtColumn()}),
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

83
            'audit_created_at' => $this->/** @scrutinizer ignore-call */ serializeDate($this->{$this->getCreatedAtColumn()}),
Loading history...
Bug introduced by
It seems like getCreatedAtColumn() 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
            'audit_created_at' => $this->serializeDate($this->{$this->/** @scrutinizer ignore-call */ getCreatedAtColumn()}),
Loading history...
84
            'audit_updated_at' => $this->serializeDate($this->{$this->getUpdatedAtColumn()}),
0 ignored issues
show
Bug introduced by
It seems like getUpdatedAtColumn() 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

84
            'audit_updated_at' => $this->serializeDate($this->{$this->/** @scrutinizer ignore-call */ getUpdatedAtColumn()}),
Loading history...
85
            '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

85
            'user_id'          => $this->/** @scrutinizer ignore-call */ getAttribute($morphPrefix . '_id'),
Loading history...
86
            'user_type'        => $this->getAttribute($morphPrefix . '_type'),
87
        ];
88
89
        // add resolvers data to metadata
90
        $resolverData = [];
91
        foreach (array_keys(Config::get('audit.resolvers', [])) as $name) {
92
            $resolverData['audit_' . $name] = $this->$name;
93
        }
94
        $this->data = array_merge($this->data, $resolverData);
95
96
        if ($this->user) {
97
            foreach ($this->user->getArrayableAttributes() as $attribute => $value) {
98
                $this->data['user_' . $attribute] = $value;
99
            }
100
        }
101
102
        $this->metadata = array_keys($this->data);
103
104
        // Modified Auditable attributes
105
        foreach ($this->new_values as $key => $value) {
106
            $this->data['new_' . $key] = $value;
107
        }
108
109
        foreach ($this->old_values as $key => $value) {
110
            $this->data['old_' . $key] = $value;
111
        }
112
113
        $this->modified = array_diff_key(array_keys($this->data), $this->metadata);
114
115
        return $this->data;
116
    }
117
118
    /**
119
     * Get the formatted value of an Eloquent model.
120
     *
121
     * @param Model $model
122
     * @param string $key
123
     * @param mixed $value
124
     *
125
     * @return mixed
126
     */
127
    protected function getFormattedValue(Model $model, string $key, $value)
128
    {
129
        // Apply defined get mutator
130
        if ($model->hasGetMutator($key)) {
131
            return $model->mutateAttribute($key, $value);
132
        }
133
134
        if (method_exists($model, 'hasAttributeMutator') && $model->hasAttributeMutator($key)) {
135
            return $model->mutateAttributeMarkedAttribute($key, $value);
136
        }
137
138
        if (array_key_exists(
139
            $key,
140
            $model->getCasts()
141
        ) && $model->getCasts()[$key] == 'Illuminate\Database\Eloquent\Casts\AsArrayObject') {
142
            $arrayObject = new \Illuminate\Database\Eloquent\Casts\ArrayObject(json_decode($value, true) ?: []);
143
            return $arrayObject;
144
        }
145
146
        // Cast to native PHP type
147
        if ($model->hasCast($key)) {
148
            if ($model->getCastType($key) == 'datetime' ) {
149
                $value = $this->castDatetimeUTC($model, $value);
150
            }
151
152
            unset($model->classCastCache[$key]);
153
154
            return $model->castAttribute($key, $value);
155
        }
156
157
        // Honour DateTime attribute
158
        if ($value !== null && in_array($key, $model->getDates(), true)) {
159
            return $model->asDateTime($this->castDatetimeUTC($model, $value));
160
        }
161
162
        return $value;
163
    }
164
165
    private function castDatetimeUTC($model, $value)
166
    {
167
        if (!is_string($value)) {
168
            return $value;
169
        }
170
171
        if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value)) {
172
            return Date::instance(Carbon::createFromFormat('Y-m-d', $value, Date::now('UTC')->getTimezone())->startOfDay());
173
        }
174
175
        if (preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $value)) {
176
            return Date::instance(Carbon::createFromFormat('Y-m-d H:i:s', $value, Date::now('UTC')->getTimezone()));
177
        }
178
179
        try {
180
            return Date::createFromFormat($model->getDateFormat(), $value, Date::now('UTC')->getTimezone());
181
        } catch (InvalidArgumentException $e) {
182
            return $value;
183
        }
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function getDataValue(string $key)
190
    {
191
        if (!array_key_exists($key, $this->data)) {
192
            return;
193
        }
194
195
        $value = $this->data[$key];
196
197
        // User value
198
        if ($this->user && Str::startsWith($key, 'user_')) {
199
            return $this->getFormattedValue($this->user, substr($key, 5), $value);
200
        }
201
202
        // Auditable value
203
        if ($this->auditable && Str::startsWith($key, ['new_', 'old_'])) {
204
            $attribute = substr($key, 4);
205
206
            return $this->getFormattedValue(
207
                $this->auditable,
208
                $attribute,
209
                $this->decodeAttributeValue($this->auditable, $attribute, $value)
210
            );
211
        }
212
213
        return $value;
214
    }
215
216
    /**
217
     * Decode attribute value.
218
     *
219
     * @param Contracts\Auditable $auditable
220
     * @param string $attribute
221
     * @param mixed $value
222
     *
223
     * @return mixed
224
     */
225
    protected function decodeAttributeValue(Contracts\Auditable $auditable, string $attribute, $value)
226
    {
227
        $attributeModifiers = $auditable->getAttributeModifiers();
228
229
        if (!array_key_exists($attribute, $attributeModifiers)) {
230
            return $value;
231
        }
232
233
        $attributeDecoder = $attributeModifiers[$attribute];
234
235
        if (is_subclass_of($attributeDecoder, AttributeEncoder::class)) {
236
            return call_user_func([$attributeDecoder, 'decode'], $value);
237
        }
238
239
        return $value;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245
    public function getMetadata(bool $json = false, int $options = 0, int $depth = 512)
246
    {
247
        if (empty($this->data)) {
248
            $this->resolveData();
249
        }
250
251
        $metadata = [];
252
253
        foreach ($this->metadata as $key) {
254
            $value = $this->getDataValue($key);
255
            $metadata[$key] = $value;
256
257
            if ($value instanceof DateTimeInterface) {
258
                $metadata[$key] = !is_null($this->auditable) ? $this->auditable->serializeDate($value) : $this->serializeDate($value);
259
            }
260
        }
261
262
        return $json ? json_encode($metadata, $options, $depth) : $metadata;
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268
    public function getModified(bool $json = false, int $options = 0, int $depth = 512)
269
    {
270
        if (empty($this->data)) {
271
            $this->resolveData();
272
        }
273
274
        $modified = [];
275
276
        foreach ($this->modified as $key) {
277
            $attribute = substr($key, 4);
278
            $state = substr($key, 0, 3);
279
280
            $value = $this->getDataValue($key);
281
            $modified[$attribute][$state] = $value;
282
283
            if ($value instanceof DateTimeInterface) {
284
                $modified[$attribute][$state] = !is_null($this->auditable) ? $this->auditable->serializeDate($value) : $this->serializeDate($value);
285
            }
286
        }
287
288
        return $json ? json_encode($modified, $options, $depth) : $modified;
289
    }
290
291
    /**
292
     * Get the Audit tags as an array.
293
     *
294
     * @return array
295
     */
296
    public function getTags(): array
297
    {
298
        return preg_split('/,/', $this->tags, -1, PREG_SPLIT_NO_EMPTY);
299
    }
300
}
301