Passed
Push — master ( b20096...5f8608 )
by Morten
20:26 queued 12s
created

Audit::getConnectionName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 36
    public function auditable()
41
    {
42 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

42
        return $this->/** @scrutinizer ignore-call */ morphTo();
Loading history...
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48 44
    public function user()
49
    {
50 44
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
51
52 44
        return $this->morphTo(__FUNCTION__, $morphPrefix . '_type', $morphPrefix . '_id');
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58 132
    public function getConnectionName()
59
    {
60 132
        return Config::get('audit.drivers.database.connection');
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 132
    public function getTable(): string
67
    {
68 132
        return Config::get('audit.drivers.database.table', parent::getTable());
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 44
    public function resolveData(): array
75
    {
76 44
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
77
78
        // Metadata
79 44
        $this->data = [
80 44
            'audit_id'         => $this->id,
81 44
            'audit_event'      => $this->event,
82 44
            'audit_tags'       => $this->tags,
83 44
            '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 44
            '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 44
            '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 44
            'user_type'        => $this->getAttribute($morphPrefix . '_type'),
87 44
        ];
88
89
        // add resolvers data to metadata
90 44
        $resolverData = [];
91 44
        foreach (array_keys(Config::get('audit.resolvers', [])) as $name) {
92 44
            $resolverData['audit_' . $name] = $this->$name;
93
        }
94 44
        $this->data = array_merge($this->data, $resolverData);
95
96 44
        if ($this->user) {
97 22
            foreach ($this->user->getArrayableAttributes() as $attribute => $value) {
98 22
                $this->data['user_' . $attribute] = $value;
99
            }
100
        }
101
102 44
        $this->metadata = array_keys($this->data);
103
104
        // Modified Auditable attributes
105 44
        foreach ($this->new_values as $key => $value) {
106 38
            $this->data['new_' . $key] = $value;
107
        }
108
109 44
        foreach ($this->old_values as $key => $value) {
110 12
            $this->data['old_' . $key] = $value;
111
        }
112
113 44
        $this->modified = array_diff_key(array_keys($this->data), $this->metadata);
114
115 44
        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 32
    protected function getFormattedValue(Model $model, string $key, $value)
128
    {
129
        // Apply defined get mutator
130 32
        if ($model->hasGetMutator($key)) {
131 20
            return $model->mutateAttribute($key, $value);
132
        }
133
134 32
        if (array_key_exists(
135 32
            $key,
136 32
            $model->getCasts()
137 32
        ) && $model->getCasts()[$key] == 'Illuminate\Database\Eloquent\Casts\AsArrayObject') {
138 2
            $arrayObject = new \Illuminate\Database\Eloquent\Casts\ArrayObject(json_decode($value, true));
139 2
            return $arrayObject;
140
        }
141
142
        // Cast to native PHP type
143 30
        if ($model->hasCast($key)) {
144 20
            if ($model->getCastType($key) == 'datetime' ) {
145 10
                $value = $this->castDatetimeUTC($model, $value);
146
            }
147
148 20
            return $model->castAttribute($key, $value);
149
        }
150
151
        // Honour DateTime attribute
152 26
        if ($value !== null && in_array($key, $model->getDates(), true)) {
153 6
            return $model->asDateTime($this->castDatetimeUTC($model, $value));
154
        }
155
156 26
        return $value;
157
    }
158
159 14
    private function castDatetimeUTC($model, $value)
160
    {
161 14
        if (!is_string($value)) {
162 2
            return $value;
163
        }
164
165 12
        if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value)) {
166
            return Date::instance(Carbon::createFromFormat('Y-m-d', $value, Date::now('UTC')->getTimezone())->startOfDay());
167
        }
168
169 12
        if (preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $value)) {
170 12
            return Date::instance(Carbon::createFromFormat('Y-m-d H:i:s', $value, Date::now('UTC')->getTimezone()));
171
        }
172
173
        try {
174
            return Date::createFromFormat($model->getDateFormat(), $value, Date::now('UTC')->getTimezone());
175
        } catch (InvalidArgumentException $e) {
176
            return $value;
177
        }
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183 36
    public function getDataValue(string $key)
184
    {
185 36
        if (!array_key_exists($key, $this->data)) {
186 2
            return;
187
        }
188
189 36
        $value = $this->data[$key];
190
191
        // User value
192 36
        if ($this->user && Str::startsWith($key, 'user_')) {
193 6
            return $this->getFormattedValue($this->user, substr($key, 5), $value);
194
        }
195
196
        // Auditable value
197 36
        if ($this->auditable && Str::startsWith($key, ['new_', 'old_'])) {
198 28
            $attribute = substr($key, 4);
199
200 28
            return $this->getFormattedValue(
201 28
                $this->auditable,
202 28
                $attribute,
203 28
                $this->decodeAttributeValue($this->auditable, $attribute, $value)
204 28
            );
205
        }
206
207 8
        return $value;
208
    }
209
210
    /**
211
     * Decode attribute value.
212
     *
213
     * @param Contracts\Auditable $auditable
214
     * @param string $attribute
215
     * @param mixed $value
216
     *
217
     * @return mixed
218
     */
219 28
    protected function decodeAttributeValue(Contracts\Auditable $auditable, string $attribute, $value)
220
    {
221 28
        $attributeModifiers = $auditable->getAttributeModifiers();
222
223 28
        if (!array_key_exists($attribute, $attributeModifiers)) {
224 28
            return $value;
225
        }
226
227 2
        $attributeDecoder = $attributeModifiers[$attribute];
228
229 2
        if (is_subclass_of($attributeDecoder, AttributeEncoder::class)) {
230 2
            return call_user_func([$attributeDecoder, 'decode'], $value);
231
        }
232
233 2
        return $value;
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239 8
    public function getMetadata(bool $json = false, int $options = 0, int $depth = 512)
240
    {
241 8
        if (empty($this->data)) {
242 8
            $this->resolveData();
243
        }
244
245 8
        $metadata = [];
246
247 8
        foreach ($this->metadata as $key) {
248 8
            $value = $this->getDataValue($key);
249 8
            $metadata[$key] = $value;
250
251 8
            if ($value instanceof DateTimeInterface) {
252 4
                $metadata[$key] = !is_null($this->auditable) ? $this->auditable->serializeDate($value) : $this->serializeDate($value);
253
            }
254
        }
255
256 8
        return $json ? json_encode($metadata, $options, $depth) : $metadata;
257
    }
258
259
    /**
260
     * {@inheritdoc}
261
     */
262 30
    public function getModified(bool $json = false, int $options = 0, int $depth = 512)
263
    {
264 30
        if (empty($this->data)) {
265 30
            $this->resolveData();
266
        }
267
268 30
        $modified = [];
269
270 30
        foreach ($this->modified as $key) {
271 26
            $attribute = substr($key, 4);
272 26
            $state = substr($key, 0, 3);
273
274 26
            $value = $this->getDataValue($key);
275 26
            $modified[$attribute][$state] = $value;
276
277 26
            if ($value instanceof DateTimeInterface) {
278 6
                $modified[$attribute][$state] = !is_null($this->auditable) ? $this->auditable->serializeDate($value) : $this->serializeDate($value);
279
            }
280
        }
281
282 30
        return $json ? json_encode($modified, $options, $depth) : $modified;
283
    }
284
285
    /**
286
     * Get the Audit tags as an array.
287
     *
288
     * @return array
289
     */
290 4
    public function getTags(): array
291
    {
292 4
        return preg_split('/,/', $this->tags, null, PREG_SPLIT_NO_EMPTY);
293
    }
294
}
295