Passed
Pull Request — master (#733)
by
unknown
11:53 queued 01:52
created

Auditable::auditSync()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.0087

Importance

Changes 6
Bugs 0 Features 1
Metric Value
cc 6
eloc 16
c 6
b 0
f 1
nc 7
nop 3
dl 0
loc 28
ccs 15
cts 16
cp 0.9375
crap 6.0087
rs 9.1111
1
<?php
2
3
namespace OwenIt\Auditing;
4
5
use Illuminate\Database\Eloquent\Relations\MorphMany;
6
use Illuminate\Database\Eloquent\SoftDeletes;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Facades\App;
9
use Illuminate\Support\Facades\Config;
10
use Illuminate\Support\Facades\Event;
11
use OwenIt\Auditing\Contracts\AttributeEncoder;
12
use OwenIt\Auditing\Contracts\AttributeRedactor;
13
use OwenIt\Auditing\Contracts\Resolver;
14
use OwenIt\Auditing\Events\AuditCustom;
15
use OwenIt\Auditing\Exceptions\AuditableTransitionException;
16
use OwenIt\Auditing\Exceptions\AuditingException;
17
18
trait Auditable
19
{
20
    /**
21
     * Auditable attributes excluded from the Audit.
22
     *
23
     * @var array
24
     */
25
    protected $excludedAttributes = [];
26
27
    /**
28
     * Audit event name.
29
     *
30
     * @var string
31
     */
32
    public $auditEvent;
33
34
    /**
35
     * Is auditing disabled?
36
     *
37
     * @var bool
38
     */
39
    public static $auditingDisabled = false;
40
41
    /**
42
     * Property may set custom event data to register
43
     * @var null|array
44
     */
45
    public $auditCustomOld = null;
46
47
    /**
48
     * Property may set custom event data to register
49
     * @var null|array
50
     */
51
    public $auditCustomNew = null;
52
53
    /**
54
     * If this is a custom event (as opposed to an eloquent event
55
     * @var bool
56
     */
57
    public $isCustomEvent = false;
58
59
    /**
60
     * Auditable boot logic.
61
     *
62
     * @return void
63
     */
64 214
    public static function bootAuditable()
65
    {
66 214
        if (!self::$auditingDisabled && static::isAuditingEnabled()) {
67 210
            static::observe(new AuditableObserver());
68
        }
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 38
    public function audits(): MorphMany
75
    {
76 38
        return $this->morphMany(
0 ignored issues
show
Bug introduced by
It seems like morphMany() 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

76
        return $this->/** @scrutinizer ignore-call */ morphMany(
Loading history...
77 38
            Config::get('audit.implementation', Models\Audit::class),
78
            'auditable'
79
        );
80
    }
81
82
    /**
83
     * Resolve the Auditable attributes to exclude from the Audit.
84
     *
85
     * @return void
86
     */
87 142
    protected function resolveAuditExclusions()
88
    {
89 142
        $this->excludedAttributes = $this->getAuditExclude();
90
91
        // When in strict mode, hidden and non visible attributes are excluded
92 142
        if ($this->getAuditStrict()) {
93
            // Hidden attributes
94 2
            $this->excludedAttributes = array_merge($this->excludedAttributes, $this->hidden);
95
96
            // Non visible attributes
97 2
            if ($this->visible) {
98 2
                $invisible = array_diff(array_keys($this->attributes), $this->visible);
99
100 2
                $this->excludedAttributes = array_merge($this->excludedAttributes, $invisible);
101
            }
102
        }
103
104
        // Exclude Timestamps
105 142
        if (!$this->getAuditTimestamps()) {
106 142
            array_push($this->excludedAttributes, $this->getCreatedAtColumn(), $this->getUpdatedAtColumn());
0 ignored issues
show
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

106
            array_push($this->excludedAttributes, $this->/** @scrutinizer ignore-call */ getCreatedAtColumn(), $this->getUpdatedAtColumn());
Loading history...
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

106
            array_push($this->excludedAttributes, $this->getCreatedAtColumn(), $this->/** @scrutinizer ignore-call */ getUpdatedAtColumn());
Loading history...
107
108 142
            if (in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))) {
109 134
                $this->excludedAttributes[] = $this->getDeletedAtColumn();
0 ignored issues
show
Bug introduced by
It seems like getDeletedAtColumn() 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

109
                /** @scrutinizer ignore-call */ 
110
                $this->excludedAttributes[] = $this->getDeletedAtColumn();
Loading history...
110
            }
111
        }
112
113
        // Valid attributes are all those that made it out of the exclusion array
114 142
        $attributes = Arr::except($this->attributes, $this->excludedAttributes);
115
116 142
        foreach ($attributes as $attribute => $value) {
117
            // Apart from null, non scalar values will be excluded
118
            if (
119 134
                is_array($value) ||
120 134
                (is_object($value) &&
121 134
                    !method_exists($value, '__toString') &&
122
                    !($value instanceof \UnitEnum))
0 ignored issues
show
Bug introduced by
The type UnitEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
123
            ) {
124 4
                $this->excludedAttributes[] = $attribute;
125
            }
126
        }
127
    }
128
129
    /**
130
     * @return array
131
     */
132 146
    public function getAuditExclude(): array
133
    {
134 146
        return $this->auditExclude ?? Config::get('audit.exclude', []);
135
    }
136
137
    /**
138
     * @return array
139
     */
140 136
    public function getAuditInclude(): array
141
    {
142 136
        return $this->auditInclude ?? [];
143
    }
144
145
    /**
146
     * Get the old/new attributes of a retrieved event.
147
     *
148
     * @return array
149
     */
150 6
    protected function getRetrievedEventAttributes(): array
151
    {
152
        // This is a read event with no attribute changes,
153
        // only metadata will be stored in the Audit
154
155
        return [
156 6
            [],
157
            [],
158
        ];
159
    }
160
161
    /**
162
     * Get the old/new attributes of a created event.
163
     *
164
     * @return array
165
     */
166 128
    protected function getCreatedEventAttributes(): array
167
    {
168 128
        $new = [];
169
170 128
        foreach ($this->attributes as $attribute => $value) {
171 120
            if ($this->isAttributeAuditable($attribute)) {
172 120
                $new[$attribute] = $value;
173
            }
174
        }
175
176
        return [
177 128
            [],
178
            $new,
179
        ];
180
    }
181
182 12
    protected function getCustomEventAttributes(): array
183
    {
184
        return [
185 12
            $this->auditCustomOld,
186 12
            $this->auditCustomNew
187
        ];
188
    }
189
190
    /**
191
     * Get the old/new attributes of an updated event.
192
     *
193
     * @return array
194
     */
195 18
    protected function getUpdatedEventAttributes(): array
196
    {
197 18
        $old = [];
198 18
        $new = [];
199
200 18
        foreach ($this->getDirty() as $attribute => $value) {
0 ignored issues
show
Bug introduced by
It seems like getDirty() 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

200
        foreach ($this->/** @scrutinizer ignore-call */ getDirty() as $attribute => $value) {
Loading history...
201 16
            if ($this->isAttributeAuditable($attribute)) {
202 14
                $old[$attribute] = Arr::get($this->original, $attribute);
203 14
                $new[$attribute] = Arr::get($this->attributes, $attribute);
204
            }
205
        }
206
207
        return [
208 18
            $old,
209
            $new,
210
        ];
211
    }
212
213
    /**
214
     * Get the old/new attributes of a deleted event.
215
     *
216
     * @return array
217
     */
218 8
    protected function getDeletedEventAttributes(): array
219
    {
220 8
        $old = [];
221
222 8
        foreach ($this->attributes as $attribute => $value) {
223 8
            if ($this->isAttributeAuditable($attribute)) {
224 8
                $old[$attribute] = $value;
225
            }
226
        }
227
228
        return [
229 8
            $old,
230
            [],
231
        ];
232
    }
233
234
    /**
235
     * Get the old/new attributes of a restored event.
236
     *
237
     * @return array
238
     */
239 4
    protected function getRestoredEventAttributes(): array
240
    {
241
        // A restored event is just a deleted event in reverse
242 4
        return array_reverse($this->getDeletedEventAttributes());
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248 164
    public function readyForAuditing(): bool
249
    {
250 164
        if (static::$auditingDisabled) {
251 2
            return false;
252
        }
253
254 164
        if ($this->isCustomEvent) {
255 12
            return true;
256
        }
257
258 164
        return $this->isEventAuditable($this->auditEvent);
259
    }
260
261
    /**
262
     * Modify attribute value.
263
     *
264
     * @param string $attribute
265
     * @param mixed $value
266
     *
267
     * @return mixed
268
     * @throws AuditingException
269
     *
270
     */
271 4
    protected function modifyAttributeValue(string $attribute, $value)
272
    {
273 4
        $attributeModifiers = $this->getAttributeModifiers();
274
275 4
        if (!array_key_exists($attribute, $attributeModifiers)) {
276 2
            return $value;
277
        }
278
279 4
        $attributeModifier = $attributeModifiers[$attribute];
280
281 4
        if (is_subclass_of($attributeModifier, AttributeRedactor::class)) {
282 2
            return call_user_func([$attributeModifier, 'redact'], $value);
283
        }
284
285 4
        if (is_subclass_of($attributeModifier, AttributeEncoder::class)) {
286 2
            return call_user_func([$attributeModifier, 'encode'], $value);
287
        }
288
289 2
        throw new AuditingException(sprintf('Invalid AttributeModifier implementation: %s', $attributeModifier));
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 152
    public function toAudit(): array
296
    {
297 152
        if (!$this->readyForAuditing()) {
298 2
            throw new AuditingException('A valid audit event has not been set');
299
        }
300
301 150
        $attributeGetter = $this->resolveAttributeGetter($this->auditEvent);
302
303 150
        if (!method_exists($this, $attributeGetter)) {
0 ignored issues
show
Bug introduced by
It seems like $attributeGetter can also be of type null; however, parameter $method of method_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

303
        if (!method_exists($this, /** @scrutinizer ignore-type */ $attributeGetter)) {
Loading history...
304 8
            throw new AuditingException(sprintf(
305
                'Unable to handle "%s" event, %s() method missing',
306 8
                $this->auditEvent,
307
                $attributeGetter
308
            ));
309
        }
310
311 142
        $this->resolveAuditExclusions();
312
313 142
        list($old, $new) = $this->$attributeGetter();
314
315 142
        if ($this->getAttributeModifiers() && !$this->isCustomEvent) {
316 4
            foreach ($old as $attribute => $value) {
317 2
                $old[$attribute] = $this->modifyAttributeValue($attribute, $value);
318
            }
319
320 4
            foreach ($new as $attribute => $value) {
321 4
                $new[$attribute] = $this->modifyAttributeValue($attribute, $value);
322
            }
323
        }
324
325 140
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
326
327 140
        $tags = implode(',', $this->generateTags());
328
329 140
        $user = $this->resolveUser();
330
331 138
        return $this->transformAudit(array_merge([
332
            'old_values'           => $old,
333
            'new_values'           => $new,
334 138
            'event'                => $this->auditEvent,
335 138
            'auditable_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

335
            'auditable_id'         => $this->/** @scrutinizer ignore-call */ getKey(),
Loading history...
336 138
            'auditable_type'       => $this->getMorphClass(),
0 ignored issues
show
Bug introduced by
It seems like getMorphClass() 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

336
            'auditable_type'       => $this->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
337 138
            $morphPrefix . '_id'   => $user ? $user->getAuthIdentifier() : null,
338 138
            $morphPrefix . '_type' => $user ? $user->getMorphClass() : null,
339 138
            'tags'                 => empty($tags) ? null : $tags,
340 138
        ], $this->runResolvers()));
341
    }
342
343
    /**
344
     * {@inheritdoc}
345
     */
346 130
    public function transformAudit(array $data): array
347
    {
348 130
        return $data;
349
    }
350
351
    /**
352
     * Resolve the User.
353
     *
354
     * @return mixed|null
355
     * @throws AuditingException
356
     *
357
     */
358 140
    protected function resolveUser()
359
    {
360 140
        $userResolver = Config::get('audit.user.resolver');
361
362 140
        if (is_null($userResolver) && Config::has('audit.resolver') && !Config::has('audit.user.resolver')) {
363
            trigger_error('The config file audit.php is not updated to the new version 13.0. Please see https://www.laravel-auditing.com/docs/13.0/upgrading',
364
                E_USER_DEPRECATED);
365
            $userResolver = Config::get('audit.resolver.user');
366
        }
367
368 140
        if (is_subclass_of($userResolver, \OwenIt\Auditing\Contracts\UserResolver::class)) {
369 138
            return call_user_func([$userResolver, 'resolve']);
370
        }
371
372 2
        throw new AuditingException('Invalid UserResolver implementation');
373
    }
374
375 138
    protected function runResolvers(): array
376
    {
377 138
        $resolved = [];
378 138
        $resolvers = Config::get('audit.resolvers', []);
379 138
        if (empty($resolvers) && Config::has('audit.resolver')) {
380
            trigger_error('The config file audit.php is not updated to the new version 13.0. Please see https://www.laravel-auditing.com/docs/13.0/upgrading',
381
                E_USER_DEPRECATED);
382
            $resolvers = Config::get('audit.resolver', []);
383
        }
384
385 138
        foreach ($resolvers as $name => $implementation) {
386 138
            if (empty($implementation)) {
387 2
                continue;
388
            }
389
390 138
            if (!is_subclass_of($implementation, Resolver::class)) {
391 6
                throw new AuditingException('Invalid Resolver implementation for: ' . $name);
392
            }
393 136
            $resolved[$name] = call_user_func([$implementation, 'resolve'], $this);
394
        }
395 132
        return $resolved;
396
    }
397
398
    /**
399
     * Determine if an attribute is eligible for auditing.
400
     *
401
     * @param string $attribute
402
     *
403
     * @return bool
404
     */
405 132
    protected function isAttributeAuditable(string $attribute): bool
406
    {
407
        // The attribute should not be audited
408 132
        if (in_array($attribute, $this->excludedAttributes, true)) {
409 126
            return false;
410
        }
411
412
        // The attribute is auditable when explicitly
413
        // listed or when the include array is empty
414 132
        $include = $this->getAuditInclude();
415
416 132
        return empty($include) || in_array($attribute, $include, true);
417
    }
418
419
    /**
420
     * Determine whether an event is auditable.
421
     *
422
     * @param string $event
423
     *
424
     * @return bool
425
     */
426 166
    protected function isEventAuditable($event): bool
427
    {
428 166
        return is_string($this->resolveAttributeGetter($event));
429
    }
430
431
    /**
432
     * Attribute getter method resolver.
433
     *
434
     * @param string $event
435
     *
436
     * @return string|null
437
     */
438 166
    protected function resolveAttributeGetter($event)
439
    {
440 166
        if (empty($event)) {
441 62
            return;
442
        }
443
444 166
        if ($this->isCustomEvent) {
445 12
            return 'getCustomEventAttributes';
446
        }
447
448 166
        foreach ($this->getAuditEvents() as $key => $value) {
449 166
            $auditableEvent = is_int($key) ? $value : $key;
450
451 166
            $auditableEventRegex = sprintf('/%s/', preg_replace('/\*+/', '.*', $auditableEvent));
452
453 166
            if (preg_match($auditableEventRegex, $event)) {
454 162
                return is_int($key) ? sprintf('get%sEventAttributes', ucfirst($event)) : $value;
455
            }
456
        }
457
    }
458
459
    /**
460
     * {@inheritdoc}
461
     */
462 166
    public function setAuditEvent(string $event): Contracts\Auditable
463
    {
464 166
        $this->auditEvent = $this->isEventAuditable($event) ? $event : null;
465
466 166
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type OwenIt\Auditing\Auditable which is incompatible with the type-hinted return OwenIt\Auditing\Contracts\Auditable.
Loading history...
467
    }
468
469
    /**
470
     * {@inheritdoc}
471
     */
472 128
    public function getAuditEvent()
473
    {
474 128
        return $this->auditEvent;
475
    }
476
477
    /**
478
     * {@inheritdoc}
479
     */
480 172
    public function getAuditEvents(): array
481
    {
482 172
        return $this->auditEvents ?? Config::get('audit.events', [
0 ignored issues
show
Bug introduced by
The property auditEvents does not exist on OwenIt\Auditing\Auditable. Did you mean auditEvent?
Loading history...
483
                'created',
484
                'updated',
485
                'deleted',
486
                'restored',
487
            ]);
488
    }
489
490
    /**
491
     * Disable Auditing.
492
     *
493
     * @return void
494
     */
495 2
    public static function disableAuditing()
496
    {
497 2
        static::$auditingDisabled = true;
498
    }
499
500
    /**
501
     * Enable Auditing.
502
     *
503
     * @return void
504
     */
505 2
    public static function enableAuditing()
506
    {
507 2
        static::$auditingDisabled = false;
508
    }
509
510
    /**
511
     * Determine whether auditing is enabled.
512
     *
513
     * @return bool
514
     */
515 218
    public static function isAuditingEnabled(): bool
516
    {
517 218
        if (App::runningInConsole()) {
518 214
            return Config::get('audit.enabled', true) && Config::get('audit.console', false);
519
        }
520
521 4
        return Config::get('audit.enabled', true);
522
    }
523
524
    /**
525
     * {@inheritdoc}
526
     */
527 148
    public function getAuditStrict(): bool
528
    {
529 148
        return $this->auditStrict ?? Config::get('audit.strict', false);
530
    }
531
532
    /**
533
     * {@inheritdoc}
534
     */
535 148
    public function getAuditTimestamps(): bool
536
    {
537 148
        return $this->auditTimestamps ?? Config::get('audit.timestamps', false);
538
    }
539
540
    /**
541
     * {@inheritdoc}
542
     */
543 136
    public function getAuditDriver()
544
    {
545 136
        return $this->auditDriver ?? Config::get('audit.driver', 'database');
546
    }
547
548
    /**
549
     * {@inheritdoc}
550
     */
551 130
    public function getAuditThreshold(): int
552
    {
553 130
        return $this->auditThreshold ?? Config::get('audit.threshold', 0);
554
    }
555
556
    /**
557
     * {@inheritdoc}
558
     */
559 142
    public function getAttributeModifiers(): array
560
    {
561 142
        return $this->attributeModifiers ?? [];
562
    }
563
564
    /**
565
     * {@inheritdoc}
566
     */
567 142
    public function generateTags(): array
568
    {
569 142
        return [];
570
    }
571
572
    /**
573
     * {@inheritdoc}
574
     */
575 22
    public function transitionTo(Contracts\Audit $audit, bool $old = false): Contracts\Auditable
576
    {
577
        // The Audit must be for an Auditable model of this type
578 22
        if ($this->getMorphClass() !== $audit->auditable_type) {
0 ignored issues
show
Bug introduced by
Accessing auditable_type on the interface OwenIt\Auditing\Contracts\Audit suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
579 4
            throw new AuditableTransitionException(sprintf(
580
                'Expected Auditable type %s, got %s instead',
581 4
                $this->getMorphClass(),
582 4
                $audit->auditable_type
583
            ));
584
        }
585
586
        // The Audit must be for this specific Auditable model
587 18
        if ($this->getKey() !== $audit->auditable_id) {
0 ignored issues
show
Bug introduced by
Accessing auditable_id on the interface OwenIt\Auditing\Contracts\Audit suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
588 4
            throw new AuditableTransitionException(sprintf(
589
                'Expected Auditable id %s, got %s instead',
590 4
                $this->getKey(),
591 4
                $audit->auditable_id
592
            ));
593
        }
594
595
        // Redacted data should not be used when transitioning states
596 14
        foreach ($this->getAttributeModifiers() as $attribute => $modifier) {
597 2
            if (is_subclass_of($modifier, AttributeRedactor::class)) {
598 2
                throw new AuditableTransitionException('Cannot transition states when an AttributeRedactor is set');
599
            }
600
        }
601
602
        // The attribute compatibility between the Audit and the Auditable model must be met
603 12
        $modified = $audit->getModified();
604
605 12
        if ($incompatibilities = array_diff_key($modified, $this->getAttributes())) {
0 ignored issues
show
Bug introduced by
It seems like $modified can also be of type string; however, parameter $array1 of array_diff_key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

605
        if ($incompatibilities = array_diff_key(/** @scrutinizer ignore-type */ $modified, $this->getAttributes())) {
Loading history...
Bug introduced by
The method getAttributes() does not exist on OwenIt\Auditing\Auditable. Did you maybe mean getAttributeModifiers()? ( Ignorable by Annotation )

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

605
        if ($incompatibilities = array_diff_key($modified, $this->/** @scrutinizer ignore-call */ getAttributes())) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
606 2
            throw new AuditableTransitionException(sprintf(
607
                'Incompatibility between [%s:%s] and [%s:%s]',
608 2
                $this->getMorphClass(),
609 2
                $this->getKey(),
610 2
                get_class($audit),
611 2
                $audit->getKey()
0 ignored issues
show
Bug introduced by
The method getKey() does not exist on OwenIt\Auditing\Contracts\Audit. Since it exists in all sub-types, consider adding an abstract or default implementation to OwenIt\Auditing\Contracts\Audit. ( Ignorable by Annotation )

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

611
                $audit->/** @scrutinizer ignore-call */ 
612
                        getKey()
Loading history...
612 2
            ), array_keys($incompatibilities));
613
        }
614
615 10
        $key = $old ? 'old' : 'new';
616
617 10
        foreach ($modified as $attribute => $value) {
618 6
            if (array_key_exists($key, $value)) {
619 6
                $this->setAttribute($attribute, $value[$key]);
0 ignored issues
show
Bug introduced by
It seems like setAttribute() 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

619
                $this->/** @scrutinizer ignore-call */ 
620
                       setAttribute($attribute, $value[$key]);
Loading history...
620
            }
621
        }
622
623 10
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type OwenIt\Auditing\Auditable which is incompatible with the type-hinted return OwenIt\Auditing\Contracts\Auditable.
Loading history...
624
    }
625
626
    /*
627
    |--------------------------------------------------------------------------
628
    | Pivot help methods
629
    |--------------------------------------------------------------------------
630
    |
631
    | Methods for auditing pivot actions
632
    |
633
    */
634
635
    /**
636
     * @param string $relationName
637
     * @param mixed $id
638
     * @param array $attributes
639
     * @param bool $touch
640
     * @return void
641
     * @throws AuditingException
642
     */
643 2
    public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['name'])
644
    {
645 2
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) {
646
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method attach');
647
        }
648 2
        $this->auditEvent = 'attach';
649 2
        $this->isCustomEvent = true;
650 2
        $this->auditCustomOld = [
651 2
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
652
        ];
653 2
        $this->{$relationName}()->attach($id, $attributes, $touch);
654 2
        $this->auditCustomNew = [
655 2
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
656
        ];
657 2
        Event::dispatch(AuditCustom::class, [$this]);
658 2
        $this->isCustomEvent = false;
659
    }
660
661
    /**
662
     * @param string $relationName
663
     * @param mixed $ids
664
     * @param bool $touch
665
     * @return int
666
     * @throws AuditingException
667
     */
668
    public function auditDetach(string $relationName, $ids = null, $touch = true)
669
    {
670
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) {
671
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method detach');
672
        }
673
674
        $this->auditEvent = 'detach';
675
        $this->isCustomEvent = true;
676
        $this->auditCustomOld = [
677
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
678
        ];
679
        $results = $this->{$relationName}()->detach($ids, $touch);
680
        $this->auditCustomNew = [
681
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
682
        ];
683
        Event::dispatch(AuditCustom::class, [$this]);
684
        $this->isCustomEvent = false;
685
        return empty($results) ? 0 : $results;
686
    }
687
688
    /**
689
     * @param $relationName
690
     * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
691
     * @param bool $detaching
692
     * @param bool $skipUnchanged
693
     * @return array
694
     * @throws AuditingException
695
     */
696 8
    public function auditSync($relationName, $ids, $detaching = true)
697
    {
698 8
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'sync')) {
699
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method sync');
700
        }
701
702 8
        $this->auditEvent = 'sync';
703
704 8
        $this->auditCustomOld = [
705 8
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
706
        ];
707
708 8
        $changes = $this->{$relationName}()->sync($ids, $detaching);
709
710 8
        if (collect($changes)->flatten()->isEmpty()) {
711 4
            $this->auditCustomOld = [];
712 4
            $this->auditCustomNew = [];
713
        } else {
714 4
            $this->auditCustomNew = [
715 4
                $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
716
            ];
717
        }
718
719 8
        $this->isCustomEvent = true;
720 8
        Event::dispatch(AuditCustom::class, [$this]);
721 8
        $this->isCustomEvent = false;
722
723 8
        return $changes;
724
    }
725
726
    /**
727
     * @param string $relationName
728
     * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
729
     * @param bool $skipUnchanged
730
     * @return array
731
     * @throws AuditingException
732
     */
733
    public function auditSyncWithoutDetaching(string $relationName, $ids)
734
    {
735
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'syncWithoutDetaching')) {
736
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method syncWithoutDetaching');
737
        }
738
        return $this->auditSync($relationName, $ids, false);
739
    }
740
}
741