Test Failed
Pull Request — master (#752)
by
unknown
08:08
created

Auditable::resolveAuditExclusions()   B

Complexity

Conditions 10
Paths 27

Size

Total Lines 38
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 12.5437

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 10
eloc 18
c 3
b 1
f 0
nc 27
nop 0
dl 0
loc 38
ccs 12
cts 17
cp 0.7059
crap 12.5437
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 108
    public static function bootAuditable()
65
    {
66 108
        if (!self::$auditingDisabled && static::isAuditingEnabled()) {
67 104
            static::observe(new AuditableObserver());
68
        }
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 34
    public function audits(): MorphMany
75
    {
76 34
        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 34
            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 90
    protected function resolveAuditExclusions()
88
    {
89 90
        $this->excludedAttributes = $this->getAuditExclude();
90
91
        // When in strict mode, hidden and non visible attributes are excluded
92 90
        if ($this->getAuditStrict()) {
93
            // Hidden attributes
94
            $this->excludedAttributes = array_merge($this->excludedAttributes, $this->hidden);
95
96
            // Non visible attributes
97
            if ($this->visible) {
98
                $invisible = array_diff(array_keys($this->attributes), $this->visible);
99
100
                $this->excludedAttributes = array_merge($this->excludedAttributes, $invisible);
101
            }
102
        }
103
104
        // Exclude Timestamps
105 90
        if (!$this->getAuditTimestamps()) {
106 90
            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 90
            if (in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))) {
109 82
                $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 90
        $attributes = Arr::except($this->attributes, $this->excludedAttributes);
115
116 90
        foreach ($attributes as $attribute => $value) {
117
            // Apart from null, non scalar values will be excluded
118
            if (
119 90
                is_array($value) ||
120 90
                (is_object($value) &&
121 90
                    !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
                $this->excludedAttributes[] = $attribute;
125
            }
126
        }
127
    }
128
129
    /**
130
     * @return array
131
     */
132 90
    public function getAuditExclude(): array
133
    {
134 90
        return $this->auditExclude ?? Config::get('audit.exclude', []);
135
    }
136
137
    /**
138
     * @return array
139
     */
140 88
    public function getAuditInclude(): array
141
    {
142 88
        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 78
    protected function getCreatedEventAttributes(): array
167
    {
168 78
        $new = [];
169
170 78
        foreach ($this->attributes as $attribute => $value) {
171 78
            if ($this->isAttributeAuditable($attribute)) {
172 78
                $new[$attribute] = $value;
173
            }
174
        }
175
176
        return [
177 78
            [],
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 14
    protected function getUpdatedEventAttributes(): array
196
    {
197 14
        $old = [];
198 14
        $new = [];
199
200 14
        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 12
            if ($this->isAttributeAuditable($attribute)) {
202 10
                $old[$attribute] = Arr::get($this->original, $attribute);
203 10
                $new[$attribute] = Arr::get($this->attributes, $attribute);
204
            }
205
        }
206
207
        return [
208 14
            $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 96
    public function readyForAuditing(): bool
249
    {
250 96
        if (static::$auditingDisabled) {
251 2
            return false;
252
        }
253
254 96
        if ($this->isCustomEvent) {
255 12
            return true;
256
        }
257
258 96
        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
    protected function modifyAttributeValue(string $attribute, $value)
272
    {
273
        $attributeModifiers = $this->getAttributeModifiers();
274
275
        if (!array_key_exists($attribute, $attributeModifiers)) {
276
            return $value;
277
        }
278
279
        $attributeModifier = $attributeModifiers[$attribute];
280
281
        if (is_subclass_of($attributeModifier, AttributeRedactor::class)) {
282
            return call_user_func([$attributeModifier, 'redact'], $value);
283
        }
284
285
        if (is_subclass_of($attributeModifier, AttributeEncoder::class)) {
286
            return call_user_func([$attributeModifier, 'encode'], $value);
287
        }
288
289
        throw new AuditingException(sprintf('Invalid AttributeModifier implementation: %s', $attributeModifier));
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 90
    public function toAudit(): array
296
    {
297 90
        if (!$this->readyForAuditing()) {
298
            throw new AuditingException('A valid audit event has not been set');
299
        }
300
301 90
        $attributeGetter = $this->resolveAttributeGetter($this->auditEvent);
302
303 90
        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
            throw new AuditingException(sprintf(
305
                'Unable to handle "%s" event, %s() method missing',
306
                $this->auditEvent,
307
                $attributeGetter
308
            ));
309
        }
310
311 90
        $this->resolveAuditExclusions();
312
313 90
        list($old, $new) = $this->$attributeGetter();
314
315 90
        if ($this->getAttributeModifiers() && !$this->isCustomEvent) {
316
            foreach ($old as $attribute => $value) {
317
                $old[$attribute] = $this->modifyAttributeValue($attribute, $value);
318
            }
319
320
            foreach ($new as $attribute => $value) {
321
                $new[$attribute] = $this->modifyAttributeValue($attribute, $value);
322
            }
323
        }
324
325 90
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
326
327 90
        $tags = implode(',', $this->generateTags());
328
329 90
        $user = $this->resolveUser();
330
331 90
        return $this->transformAudit(array_merge([
332
            'old_values'           => $old,
333
            'new_values'           => $new,
334 90
            'event'                => $this->auditEvent,
335 90
            '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 90
            '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 90
            $morphPrefix . '_id'   => $user ? $user->getAuthIdentifier() : null,
338 90
            $morphPrefix . '_type' => $user ? $user->getMorphClass() : null,
339 90
            'tags'                 => empty($tags) ? null : $tags,
340 90
        ], $this->runResolvers()));
341
    }
342
343
    /**
344
     * {@inheritdoc}
345
     */
346 90
    public function transformAudit(array $data): array
347
    {
348 90
        return $data;
349
    }
350
351
    /**
352
     * Resolve the User.
353
     *
354
     * @return mixed|null
355
     * @throws AuditingException
356
     *
357
     */
358 90
    protected function resolveUser()
359
    {
360 90
        $userResolver = Config::get('audit.user.resolver');
361
362 90
        if (is_null($userResolver) && Config::has('audit.resolver') && !Config::has('audit.user.resolver')) {
363
            trigger_error(
364
                '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',
365
                E_USER_DEPRECATED
366
            );
367
            $userResolver = Config::get('audit.resolver.user');
368
        }
369
370 90
        if (is_subclass_of($userResolver, \OwenIt\Auditing\Contracts\UserResolver::class)) {
371 90
            return call_user_func([$userResolver, 'resolve']);
372
        }
373
374
        throw new AuditingException('Invalid UserResolver implementation');
375
    }
376
377 90
    protected function runResolvers(): array
378
    {
379 90
        $resolved = [];
380 90
        $resolvers = Config::get('audit.resolvers', []);
381 90
        if (empty($resolvers) && Config::has('audit.resolver')) {
382
            trigger_error(
383
                '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',
384
                E_USER_DEPRECATED
385
            );
386
            $resolvers = Config::get('audit.resolver', []);
387
        }
388
389 90
        foreach ($resolvers as $name => $implementation) {
390 90
            if (empty($implementation)) {
391 2
                continue;
392
            }
393
394 90
            if (!is_subclass_of($implementation, Resolver::class)) {
395
                throw new AuditingException('Invalid Resolver implementation for: ' . $name);
396
            }
397 90
            $resolved[$name] = call_user_func([$implementation, 'resolve'], $this);
398
        }
399 90
        return $resolved;
400
    }
401
402
    /**
403
     * Determine if an attribute is eligible for auditing.
404
     *
405
     * @param string $attribute
406
     *
407
     * @return bool
408
     */
409 88
    protected function isAttributeAuditable(string $attribute): bool
410
    {
411
        // The attribute should not be audited
412 88
        if (in_array($attribute, $this->excludedAttributes, true)) {
413 85
            return false;
414
        }
415
416
        // The attribute is auditable when explicitly
417
        // listed or when the include array is empty
418 88
        $include = $this->getAuditInclude();
419
420 88
        return empty($include) || in_array($attribute, $include, true);
421
    }
422
423
    /**
424
     * Determine whether an event is auditable.
425
     *
426
     * @param string $event
427
     *
428
     * @return bool
429
     */
430 98
    protected function isEventAuditable($event): bool
431
    {
432 98
        return is_string($this->resolveAttributeGetter($event));
433
    }
434
435
    /**
436
     * Attribute getter method resolver.
437
     *
438
     * @param string $event
439
     *
440
     * @return string|null
441
     */
442 98
    protected function resolveAttributeGetter($event)
443
    {
444 98
        if (empty($event)) {
445 42
            return;
446
        }
447
448 98
        if ($this->isCustomEvent) {
449 12
            return 'getCustomEventAttributes';
450
        }
451
452 98
        foreach ($this->getAuditEvents() as $key => $value) {
453 98
            $auditableEvent = is_int($key) ? $value : $key;
454
455 98
            $auditableEventRegex = sprintf('/%s/', preg_replace('/\*+/', '.*', $auditableEvent));
456
457 98
            if (preg_match($auditableEventRegex, $event)) {
458 98
                return is_int($key) ? sprintf('get%sEventAttributes', ucfirst($event)) : $value;
459
            }
460
        }
461
    }
462
463
    /**
464
     * {@inheritdoc}
465
     */
466 98
    public function setAuditEvent(string $event): Contracts\Auditable
467
    {
468 98
        $this->auditEvent = $this->isEventAuditable($event) ? $event : null;
469
470 98
        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...
471
    }
472
473
    /**
474
     * {@inheritdoc}
475
     */
476 94
    public function getAuditEvent()
477
    {
478 94
        return $this->auditEvent;
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484 102
    public function getAuditEvents(): array
485
    {
486 102
        return (property_exists($this, 'auditEvents') ? $this->auditEvents : null) ?: Config::get('audit.events', [
487
                'created',
488
                'updated',
489
                'deleted',
490
                'restored',
491
            ]);
492
    }
493
494
    /**
495
     * Disable Auditing.
496
     *
497
     * @return void
498
     */
499 2
    public static function disableAuditing()
500
    {
501 2
        static::$auditingDisabled = true;
502
    }
503
504
    /**
505
     * Enable Auditing.
506
     *
507
     * @return void
508
     */
509 2
    public static function enableAuditing()
510
    {
511 2
        static::$auditingDisabled = false;
512
    }
513
514
    /**
515
     * Determine whether auditing is enabled.
516
     *
517
     * @return bool
518
     */
519 112
    public static function isAuditingEnabled(): bool
520
    {
521 112
        if (App::runningInConsole()) {
522 108
            return Config::get('audit.enabled', true) && Config::get('audit.console', false);
523
        }
524
525 4
        return Config::get('audit.enabled', true);
526
    }
527
528
    /**
529
     * {@inheritdoc}
530
     */
531 90
    public function getAuditStrict(): bool
532
    {
533 90
        return $this->auditStrict ?? Config::get('audit.strict', false);
534
    }
535
536
    /**
537
     * {@inheritdoc}
538
     */
539 90
    public function getAuditTimestamps(): bool
540
    {
541 90
        return $this->auditTimestamps ?? Config::get('audit.timestamps', false);
542
    }
543
544
    /**
545
     * {@inheritdoc}
546
     */
547 96
    public function getAuditDriver()
548
    {
549 96
        return $this->auditDriver ?? Config::get('audit.driver', 'database');
550
    }
551
552
    /**
553
     * {@inheritdoc}
554
     */
555 90
    public function getAuditThreshold(): int
556
    {
557 90
        return $this->auditThreshold ?? Config::get('audit.threshold', 0);
558
    }
559
560
    /**
561
     * {@inheritdoc}
562
     */
563 90
    public function getAttributeModifiers(): array
564
    {
565 90
        return $this->attributeModifiers ?? [];
566
    }
567
568
    /**
569
     * {@inheritdoc}
570
     */
571 90
    public function generateTags(): array
572
    {
573 90
        return [];
574
    }
575
576
    /**
577
     * {@inheritdoc}
578
     */
579
    public function transitionTo(Contracts\Audit $audit, bool $old = false): Contracts\Auditable
580
    {
581
        // The Audit must be for an Auditable model of this type
582
        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...
583
            throw new AuditableTransitionException(sprintf(
584
                'Expected Auditable type %s, got %s instead',
585
                $this->getMorphClass(),
586
                $audit->auditable_type
587
            ));
588
        }
589
590
        // The Audit must be for this specific Auditable model
591
        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...
592
            throw new AuditableTransitionException(sprintf(
593
                'Expected Auditable id %s, got %s instead',
594
                $this->getKey(),
595
                $audit->auditable_id
596
            ));
597
        }
598
599
        // Redacted data should not be used when transitioning states
600
        foreach ($this->getAttributeModifiers() as $attribute => $modifier) {
601
            if (is_subclass_of($modifier, AttributeRedactor::class)) {
602
                throw new AuditableTransitionException('Cannot transition states when an AttributeRedactor is set');
603
            }
604
        }
605
606
        // The attribute compatibility between the Audit and the Auditable model must be met
607
        $modified = $audit->getModified();
608
609
        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

609
        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

609
        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...
610
            throw new AuditableTransitionException(sprintf(
611
                'Incompatibility between [%s:%s] and [%s:%s]',
612
                $this->getMorphClass(),
613
                $this->getKey(),
614
                get_class($audit),
615
                $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

615
                $audit->/** @scrutinizer ignore-call */ 
616
                        getKey()
Loading history...
616
            ), array_keys($incompatibilities));
617
        }
618
619
        $key = $old ? 'old' : 'new';
620
621
        foreach ($modified as $attribute => $value) {
622
            if (array_key_exists($key, $value)) {
623
                $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

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