Test Failed
Pull Request — master (#684)
by Morten
14:54
created

Auditable::toAudit()   B

Complexity

Conditions 10
Paths 4

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 12.332

Importance

Changes 10
Bugs 2 Features 2
Metric Value
cc 10
eloc 28
c 10
b 2
f 2
nc 4
nop 0
dl 0
loc 46
ccs 20
cts 28
cp 0.7143
crap 12.332
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
    protected $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 40
    public static function bootAuditable()
65
    {
66 40
        if (!self::$auditingDisabled && static::isAuditingEnabled()) {
67 38
            static::observe(new AuditableObserver());
68
        }
69 40
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 10
    public function audits(): MorphMany
75
    {
76 10
        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 10
            Config::get('audit.implementation', Models\Audit::class),
78 10
            'auditable'
79
        );
80
    }
81
82
    /**
83
     * Resolve the Auditable attributes to exclude from the Audit.
84
     *
85
     * @return void
86
     */
87 32
    protected function resolveAuditExclusions()
88
    {
89 32
        $this->excludedAttributes = $this->getAuditExclude();
90
91
        // When in strict mode, hidden and non visible attributes are excluded
92 32
        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 32
        if (!$this->getAuditTimestamps()) {
106 32
            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 32
            if (in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))) {
109 24
                $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 32
        $attributes = Arr::except($this->attributes, $this->excludedAttributes);
115
116 32
        foreach ($attributes as $attribute => $value) {
117
            // Apart from null, non scalar values will be excluded
118 32
            if (is_array($value) || (is_object($value) && !method_exists($value, '__toString'))) {
119
                $this->excludedAttributes[] = $attribute;
120
            }
121
        }
122 32
    }
123
124
    /**
125
     * Get the old/new attributes of a retrieved event.
126
     *
127
     * @return array
128
     */
129 4
    protected function getRetrievedEventAttributes(): array
130
    {
131
        // This is a read event with no attribute changes,
132
        // only metadata will be stored in the Audit
133
134
        return [
135 4
            [],
136
            [],
137
        ];
138
    }
139
140
    /**
141
     * Get the old/new attributes of a created event.
142
     *
143
     * @return array
144
     */
145 20
    protected function getCreatedEventAttributes(): array
146
    {
147 20
        $new = [];
148
149 20
        foreach ($this->attributes as $attribute => $value) {
150 20
            if ($this->isAttributeAuditable($attribute)) {
151 20
                $new[$attribute] = $value;
152
            }
153
        }
154
155
        return [
156 20
            [],
157 20
            $new,
158
        ];
159
    }
160
161
    protected function getCustomEventAttributes(): array
162
    {
163
        return [
164
            $this->auditCustomOld,
165
            $this->auditCustomNew
166
        ];
167
    }
168
169
    /**
170
     * Get the old/new attributes of an updated event.
171
     *
172
     * @return array
173
     */
174 8
    protected function getUpdatedEventAttributes(): array
175
    {
176 8
        $old = [];
177 8
        $new = [];
178
179 8
        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

179
        foreach ($this->/** @scrutinizer ignore-call */ getDirty() as $attribute => $value) {
Loading history...
180 8
            if ($this->isAttributeAuditable($attribute)) {
181 8
                $old[$attribute] = Arr::get($this->original, $attribute);
182 8
                $new[$attribute] = Arr::get($this->attributes, $attribute);
183
            }
184
        }
185
186
        return [
187 8
            $old,
188 8
            $new,
189
        ];
190
    }
191
192
    /**
193
     * Get the old/new attributes of a deleted event.
194
     *
195
     * @return array
196
     */
197 4
    protected function getDeletedEventAttributes(): array
198
    {
199 4
        $old = [];
200
201 4
        foreach ($this->attributes as $attribute => $value) {
202 4
            if ($this->isAttributeAuditable($attribute)) {
203 4
                $old[$attribute] = $value;
204
            }
205
        }
206
207
        return [
208 4
            $old,
209
            [],
210
        ];
211
    }
212
213
    /**
214
     * Get the old/new attributes of a restored event.
215
     *
216
     * @return array
217
     */
218 2
    protected function getRestoredEventAttributes(): array
219
    {
220
        // A restored event is just a deleted event in reverse
221 2
        return array_reverse($this->getDeletedEventAttributes());
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227 38
    public function readyForAuditing(): bool
228
    {
229 38
        if (static::$auditingDisabled) {
230 2
            return false;
231
        }
232
233 38
        if ($this->isCustomEvent) {
234
            return true;
235
        }
236
237 38
        return $this->isEventAuditable($this->auditEvent);
238
    }
239
240
    /**
241
     * Modify attribute value.
242
     *
243
     * @param string $attribute
244
     * @param mixed $value
245
     *
246
     * @return mixed
247
     * @throws AuditingException
248
     *
249
     */
250
    protected function modifyAttributeValue(string $attribute, $value)
251
    {
252
        $attributeModifiers = $this->getAttributeModifiers();
253
254
        if (!array_key_exists($attribute, $attributeModifiers)) {
255
            return $value;
256
        }
257
258
        $attributeModifier = $attributeModifiers[$attribute];
259
260
        if (is_subclass_of($attributeModifier, AttributeRedactor::class)) {
261
            return call_user_func([$attributeModifier, 'redact'], $value);
262
        }
263
264
        if (is_subclass_of($attributeModifier, AttributeEncoder::class)) {
265
            return call_user_func([$attributeModifier, 'encode'], $value);
266
        }
267
268
        throw new AuditingException(sprintf('Invalid AttributeModifier implementation: %s', $attributeModifier));
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274 32
    public function toAudit(): array
275
    {
276 32
        if (!$this->readyForAuditing()) {
277
            throw new AuditingException('A valid audit event has not been set');
278
        }
279
280 32
        $attributeGetter = $this->resolveAttributeGetter($this->auditEvent);
281
282 32
        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

282
        if (!method_exists($this, /** @scrutinizer ignore-type */ $attributeGetter)) {
Loading history...
283
            throw new AuditingException(sprintf(
284
                'Unable to handle "%s" event, %s() method missing',
285
                $this->auditEvent,
286
                $attributeGetter
287
            ));
288
        }
289
290 32
        $this->resolveAuditExclusions();
291
292 32
        list($old, $new) = $this->$attributeGetter();
293
294 32
        if ($this->getAttributeModifiers() && !$this->isCustomEvent) {
295
            foreach ($old as $attribute => $value) {
296
                $old[$attribute] = $this->modifyAttributeValue($attribute, $value);
297
            }
298
299
            foreach ($new as $attribute => $value) {
300
                $new[$attribute] = $this->modifyAttributeValue($attribute, $value);
301
            }
302
        }
303
304 32
        $morphPrefix = Config::get('audit.user.morph_prefix', 'user');
305
306 32
        $tags = implode(',', $this->generateTags());
307
308 32
        $user = $this->resolveUser();
309
310 32
        return $this->transformAudit(array_merge([
311 32
            'old_values'           => $old,
312 32
            'new_values'           => $new,
313 32
            'event'                => $this->auditEvent,
314 32
            '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

314
            'auditable_id'         => $this->/** @scrutinizer ignore-call */ getKey(),
Loading history...
315 32
            '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

315
            'auditable_type'       => $this->/** @scrutinizer ignore-call */ getMorphClass(),
Loading history...
316 32
            $morphPrefix . '_id'   => $user ? $user->getAuthIdentifier() : null,
317 32
            $morphPrefix . '_type' => $user ? $user->getMorphClass() : null,
318 32
            'tags'                 => empty($tags) ? null : $tags,
319 32
        ], $this->runResolvers()));
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325 32
    public function transformAudit(array $data): array
326
    {
327 32
        return $data;
328
    }
329
330
    /**
331
     * Resolve the User.
332
     *
333
     * @return mixed|null
334
     * @throws AuditingException
335
     *
336
     */
337 32
    protected function resolveUser()
338
    {
339 32
        $userResolver = Config::get('audit.user.resolver');
340
341 32
        if (is_subclass_of($userResolver, \OwenIt\Auditing\Contracts\UserResolver::class)) {
342 32
            return call_user_func([$userResolver, 'resolve']);
343
        }
344
345
        throw new AuditingException('Invalid UserResolver implementation');
346
    }
347
348 32
    protected function runResolvers(): array
349
    {
350 32
        $resolved = [];
351 32
        foreach (Config::get('audit.resolvers', []) as $name => $implementation) {
352 32
            if (empty($implementation)) {
353 2
                continue;
354
            }
355
356 32
            if (!is_subclass_of($implementation, Resolver::class)) {
357
                throw new AuditingException('Invalid Resolver implementation for: ' . $name);
358
            }
359 32
            $resolved[$name] = call_user_func([$implementation, 'resolve'], $this);
360
        }
361 32
        return $resolved;
362
    }
363
364
    /**
365
     * Determine if an attribute is eligible for auditing.
366
     *
367
     * @param string $attribute
368
     *
369
     * @return bool
370
     */
371 30
    protected function isAttributeAuditable(string $attribute): bool
372
    {
373
        // The attribute should not be audited
374 30
        if (in_array($attribute, $this->excludedAttributes, true)) {
375 28
            return false;
376
        }
377
378
        // The attribute is auditable when explicitly
379
        // listed or when the include array is empty
380 30
        $include = $this->getAuditInclude();
381
382 30
        return empty($include) || in_array($attribute, $include, true);
383
    }
384
385
    /**
386
     * Determine whether an event is auditable.
387
     *
388
     * @param string $event
389
     *
390
     * @return bool
391
     */
392 38
    protected function isEventAuditable($event): bool
393
    {
394 38
        return is_string($this->resolveAttributeGetter($event));
395
    }
396
397
    /**
398
     * Attribute getter method resolver.
399
     *
400
     * @param string $event
401
     *
402
     * @return string|null
403
     */
404 38
    protected function resolveAttributeGetter($event)
405
    {
406 38
        if (empty($event)) {
407 16
            return;
408
        }
409
410 38
        if ($this->isCustomEvent) {
411
            return 'getCustomEventAttributes';
412
        }
413
414 38
        foreach ($this->getAuditEvents() as $key => $value) {
415 38
            $auditableEvent = is_int($key) ? $value : $key;
416
417 38
            $auditableEventRegex = sprintf('/%s/', preg_replace('/\*+/', '.*', $auditableEvent));
418
419 38
            if (preg_match($auditableEventRegex, $event)) {
420 38
                return is_int($key) ? sprintf('get%sEventAttributes', ucfirst($event)) : $value;
421
            }
422
        }
423 16
    }
424
425
    /**
426
     * {@inheritdoc}
427
     */
428 38
    public function setAuditEvent(string $event): Contracts\Auditable
429
    {
430 38
        $this->auditEvent = $this->isEventAuditable($event) ? $event : null;
431
432 38
        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...
433
    }
434
435
    public function setAuditExcludedAttributes(array $excludedAttributes)
436
    {
437
        $this->auditExclude = $excludedAttributes;
0 ignored issues
show
Bug Best Practice introduced by
The property auditExclude does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function getAuditEvent()
444
    {
445
        return $this->auditEvent;
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451 38
    public function getAuditEvents(): array
452
    {
453 38
        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...
454 38
                'created',
455
                'updated',
456
                'deleted',
457
                'restored',
458
            ]);
459
    }
460
461
    /**
462
     * Disable Auditing.
463
     *
464
     * @return void
465
     */
466 2
    public static function disableAuditing()
467
    {
468 2
        static::$auditingDisabled = true;
469 2
    }
470
471
    /**
472
     * Enable Auditing.
473
     *
474
     * @return void
475
     */
476 2
    public static function enableAuditing()
477
    {
478 2
        static::$auditingDisabled = false;
479 2
    }
480
481
    /**
482
     * Determine whether auditing is enabled.
483
     *
484
     * @return bool
485
     */
486 40
    public static function isAuditingEnabled(): bool
487
    {
488 40
        if (App::runningInConsole()) {
489 38
            return Config::get('audit.enabled', true) && Config::get('audit.console', false);
490
        }
491
492 2
        return Config::get('audit.enabled', true);
493
    }
494
495
    /**
496
     * {@inheritdoc}
497
     */
498 30
    public function getAuditInclude(): array
499
    {
500 30
        return $this->auditInclude ?? [];
0 ignored issues
show
Bug introduced by
The property auditInclude does not exist on OwenIt\Auditing\Auditable. Did you mean auditExclude?
Loading history...
501
    }
502
503
    /**
504
     * {@inheritdoc}
505
     */
506 32
    public function getAuditExclude(): array
507
    {
508 32
        return $this->auditExclude ?? [];
509
    }
510
511
    /**
512
     * {@inheritdoc}
513
     */
514 32
    public function getAuditStrict(): bool
515
    {
516 32
        return $this->auditStrict ?? Config::get('audit.strict', false);
517
    }
518
519
    /**
520
     * {@inheritdoc}
521
     */
522 32
    public function getAuditTimestamps(): bool
523
    {
524 32
        return $this->auditTimestamps ?? Config::get('audit.timestamps', false);
525
    }
526
527
    /**
528
     * {@inheritdoc}
529
     */
530 38
    public function getAuditDriver()
531
    {
532 38
        return $this->auditDriver ?? Config::get('audit.driver', 'database');
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     */
538 32
    public function getAuditThreshold(): int
539
    {
540 32
        return $this->auditThreshold ?? Config::get('audit.threshold', 0);
541
    }
542
543
    /**
544
     * {@inheritdoc}
545
     */
546 32
    public function getAttributeModifiers(): array
547
    {
548 32
        return $this->attributeModifiers ?? [];
549
    }
550
551
    /**
552
     * {@inheritdoc}
553
     */
554 32
    public function generateTags(): array
555
    {
556 32
        return [];
557
    }
558
559
    /**
560
     * {@inheritdoc}
561
     */
562
    public function transitionTo(Contracts\Audit $audit, bool $old = false): Contracts\Auditable
563
    {
564
        // The Audit must be for an Auditable model of this type
565
        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...
566
            throw new AuditableTransitionException(sprintf(
567
                'Expected Auditable type %s, got %s instead',
568
                $this->getMorphClass(),
569
                $audit->auditable_type
570
            ));
571
        }
572
573
        // The Audit must be for this specific Auditable model
574
        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...
575
            throw new AuditableTransitionException(sprintf(
576
                'Expected Auditable id %s, got %s instead',
577
                $this->getKey(),
578
                $audit->auditable_id
579
            ));
580
        }
581
582
        // Redacted data should not be used when transitioning states
583
        foreach ($this->getAttributeModifiers() as $attribute => $modifier) {
584
            if (is_subclass_of($modifier, AttributeRedactor::class)) {
585
                throw new AuditableTransitionException('Cannot transition states when an AttributeRedactor is set');
586
            }
587
        }
588
589
        // The attribute compatibility between the Audit and the Auditable model must be met
590
        $modified = $audit->getModified();
591
592
        if ($incompatibilities = array_diff_key($modified, $this->getAttributes())) {
0 ignored issues
show
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

592
        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...
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

592
        if ($incompatibilities = array_diff_key(/** @scrutinizer ignore-type */ $modified, $this->getAttributes())) {
Loading history...
593
            throw new AuditableTransitionException(sprintf(
594
                'Incompatibility between [%s:%s] and [%s:%s]',
595
                $this->getMorphClass(),
596
                $this->getKey(),
597
                get_class($audit),
598
                $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

598
                $audit->/** @scrutinizer ignore-call */ 
599
                        getKey()
Loading history...
599
            ), array_keys($incompatibilities));
600
        }
601
602
        $key = $old ? 'old' : 'new';
603
604
        foreach ($modified as $attribute => $value) {
605
            if (array_key_exists($key, $value)) {
606
                $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

606
                $this->/** @scrutinizer ignore-call */ 
607
                       setAttribute($attribute, $value[$key]);
Loading history...
607
            }
608
        }
609
610
        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...
611
    }
612
613
    /*
614
    |--------------------------------------------------------------------------
615
    | Pivot help methods
616
    |--------------------------------------------------------------------------
617
    |
618
    | Methods for auditing pivot actions
619
    |
620
    */
621
622
    /**
623
     * @param string $relationName
624
     * @param mixed $id
625
     * @param array $attributes
626
     * @param bool $touch
627
     * @return void
628
     * @throws AuditingException
629
     */
630
    public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true)
631
    {
632
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) {
633
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method attach');
634
        }
635
        $this->auditEvent = 'attach';
636
        $this->isCustomEvent = true;
637
        $this->auditCustomOld = [
638
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
639
        ];
640
        $this->{$relationName}()->attach($id, $attributes, $touch);
641
        $this->auditCustomNew = [
642
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
643
        ];
644
        Event::dispatch(AuditCustom::class, [$this]);
645
    }
646
647
    /**
648
     * @param string $relationName
649
     * @param mixed $ids
650
     * @param bool $touch
651
     * @return int
652
     * @throws AuditingException
653
     */
654
    public function auditDetach(string $relationName, $ids = null, $touch = true)
655
    {
656
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) {
657
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method detach');
658
        }
659
660
        $this->auditEvent = 'detach';
661
        $this->isCustomEvent = true;
662
        $this->auditCustomOld = [
663
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
664
        ];
665
        $results = $this->{$relationName}()->detach($ids, $touch);
666
        $this->auditCustomNew = [
667
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
668
        ];
669
        Event::dispatch(AuditCustom::class, [$this]);
670
        return empty($results) ? 0 : $results;
671
    }
672
673
    /**
674
     * @param $relationName
675
     * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
676
     * @param bool $detaching
677
     * @return array
678
     * @throws AuditingException
679
     */
680
    public function auditSync($relationName, $ids, $detaching = true)
681
    {
682
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'sync')) {
683
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method sync');
684
        }
685
686
        $this->auditEvent = 'sync';
687
        $this->isCustomEvent = true;
688
        $this->auditCustomOld = [
689
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
690
        ];
691
        $changes = $this->{$relationName}()->sync($ids, $detaching);
692
        $this->auditCustomNew = [
693
            $relationName => $this->{$relationName}()->get()->isEmpty() ? [] : $this->{$relationName}()->get()->toArray()
694
        ];
695
        Event::dispatch(AuditCustom::class, [$this]);
696
        return $changes;
697
    }
698
699
    /**
700
     * @param string $relationName
701
     * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
702
     * @return array
703
     * @throws AuditingException
704
     */
705
    public function auditSyncWithoutDetaching(string $relationName, $ids)
706
    {
707
        if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'syncWithoutDetaching')) {
708
            throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method syncWithoutDetaching');
709
        }
710
        return $this->auditSync($relationName, $ids, false);
711
    }
712
}
713