diff --git a/src/Auditable.php b/src/Auditable.php index 3696afb2..950a6bc6 100644 --- a/src/Auditable.php +++ b/src/Auditable.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -13,6 +14,7 @@ use Illuminate\Support\Facades\Event; use OwenIt\Auditing\Contracts\AttributeEncoder; use OwenIt\Auditing\Contracts\AttributeRedactor; +use OwenIt\Auditing\Contracts\Auditable as ContractsAuditable; use OwenIt\Auditing\Contracts\Resolver; use OwenIt\Auditing\Events\AuditCustom; use OwenIt\Auditing\Exceptions\AuditableTransitionException; @@ -746,7 +748,17 @@ public function auditDetach(string $relationName, $ids = null, $touch = true, $c } $old = $relationCall->get($columns); - $results = $relationCall->detach($ids, $touch); + + $pivotClass = $relationCall->getPivotClass(); + + if ($pivotClass !== Pivot::class && is_a($pivotClass, ContractsAuditable::class, true)) { + $results = $pivotClass::withoutAuditing(function () use ($relationCall, $ids, $touch) { + return $relationCall->detach($ids, $touch); + }); + } else { + $results = $relationCall->detach($ids, $touch); + } + $new = $relationCall->get($columns); $this->dispatchRelationAuditEvent($relationName, 'detach', $old, $new); @@ -774,7 +786,16 @@ public function auditSync(string $relationName, $ids, $detaching = true, $column } $old = $relationCall->get($columns); - $changes = $relationCall->sync($ids, $detaching); + + $pivotClass = $relationCall->getPivotClass(); + + if ($pivotClass !== Pivot::class && is_a($pivotClass, ContractsAuditable::class, true)) { + $changes = $pivotClass::withoutAuditing(function () use ($relationCall, $ids, $detaching) { + return $relationCall->sync($ids, $detaching); + }); + } else { + $changes = $relationCall->sync($ids, $detaching); + } if (collect($changes)->flatten()->isEmpty()) { $old = $new = collect([]); diff --git a/tests/Functional/AuditingTest.php b/tests/Functional/AuditingTest.php index 6bc063d6..e79ce79e 100644 --- a/tests/Functional/AuditingTest.php +++ b/tests/Functional/AuditingTest.php @@ -20,6 +20,7 @@ use OwenIt\Auditing\Tests\Models\ArticleCustomAuditMorph; use OwenIt\Auditing\Tests\Models\ArticleExcludes; use OwenIt\Auditing\Tests\Models\Category; +use OwenIt\Auditing\Tests\Models\Group; use OwenIt\Auditing\Tests\Models\User; class AuditingTest extends AuditingTestCase @@ -1092,4 +1093,72 @@ public function canAuditCustomAuditModelImplementation() $this->assertNotEmpty($audit); $this->assertSame(get_class($audit), \OwenIt\Auditing\Tests\Models\CustomAudit::class); } + + /** + * @test + * @return void + */ + public function itWillAuditSyncWithAuditablePivotClass() + { + $group = factory(Group::class)->create(); + $user = factory(User::class)->create(); + + $no_of_audits_before = Audit::where('auditable_type', User::class)->count(); + + $user->auditSync('groups', [$group->getKey() => ["role" => "admin"]]); + + $no_of_audits_mid = Audit::where('auditable_type', User::class)->count(); + $memberRole = $user->groups()->first()->pivot->role; + + $user->auditSync('groups', []); + + $no_of_audits_after = Audit::where('auditable_type', User::class)->count(); + + $this->assertSame("admin", $memberRole); + $this->assertGreaterThan($no_of_audits_before, $no_of_audits_mid); + $this->assertGreaterThan($no_of_audits_mid, $no_of_audits_after); + } + + /** + * @test + * @return void + */ + public function itWillAuditAttachWithAuditablePivotClass() + { + $group = factory(Group::class)->create(); + $user = factory(User::class)->create(); + + $no_of_audits_before = Audit::where('auditable_type', User::class)->count(); + + $user->auditAttach('groups', $group); + + $attachedGroup = $user->groups()->first()->getKey(); + $no_of_audits_after = Audit::where('auditable_type', User::class)->count(); + + $this->assertSame($group->getKey(), $attachedGroup); + $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); + } + + /** + * @test + * @return void + */ + public function itWillAuditDetachWithAuditablePivotClass() + { + $group = factory(Group::class)->create(); + $user = factory(User::class)->create(); + + $user->groups()->attach($group); + + $attachedGroup = $user->groups()->first()->getKey(); + $no_of_audits_before = Audit::where('auditable_type', User::class)->count(); + + $detachedGroups = $user->auditDetach('groups', $group); + + $no_of_audits_after = Audit::where('auditable_type', User::class)->count(); + + $this->assertSame($group->getKey(), $attachedGroup); + $this->assertSame(1, $detachedGroups); + $this->assertGreaterThan($no_of_audits_before, $no_of_audits_after); + } } diff --git a/tests/Models/Group.php b/tests/Models/Group.php new file mode 100644 index 00000000..5e13d42d --- /dev/null +++ b/tests/Models/Group.php @@ -0,0 +1,7 @@ +belongsToMany(Group::class, 'group_members', 'user_id', 'group_id')->using(GroupMember::class)->withPivot('id','role'); + } } diff --git a/tests/database/factories/GroupFactory.php b/tests/database/factories/GroupFactory.php new file mode 100644 index 00000000..f9789ef1 --- /dev/null +++ b/tests/database/factories/GroupFactory.php @@ -0,0 +1,16 @@ +define(\OwenIt\Auditing\Tests\Models\Group::class, function (Faker $faker) { + return [ + 'name' => $faker->unique()->colorName(), + ]; +}); diff --git a/tests/database/migrations/0000_00_00_000004_create_groups_test_table.php b/tests/database/migrations/0000_00_00_000004_create_groups_test_table.php new file mode 100644 index 00000000..b5d985d0 --- /dev/null +++ b/tests/database/migrations/0000_00_00_000004_create_groups_test_table.php @@ -0,0 +1,41 @@ +increments('id'); + $table->string('name'); + $table->timestamps(); + }); + + Schema::create('group_members', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedBigInteger('user_id'); + $table->unsignedBigInteger('group_id'); + $table->enum('role',['member','admin'])->default('member'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('group_members'); + Schema::drop('groups'); + } +}