diff --git a/src/Models/Link.php b/src/Models/Link.php index dbebf719..02532bd9 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -342,6 +342,67 @@ public function Owner(): ?DataObject return $owner; } + public function canView($member = null) + { + return $this->canPerformAction(__FUNCTION__, $member); + } + + public function canEdit($member = null) + { + return $this->canPerformAction(__FUNCTION__, $member); + } + + public function canDelete($member = null) + { + return $this->canPerformAction(__FUNCTION__, $member); + } + + public function canCreate($member = null, $context = []) + { + // Allow extensions to override permission checks + $results = $this->extendedCan(__FUNCTION__, $member, $context); + if (isset($results)) { + return $results; + } + + // Assume anyone can create a link by default - there's no way to determine + // what the "owner" record is going to be ahead of time, but if the user + // can't edit the owner then the linkfield will be read-only anyway, so we + // can rely on that to determine who can create links. + return true; + } + + public function can($perm, $member = null, $context = []) + { + $check = ucfirst(strtolower($perm)); + return match ($check) { + 'View', 'Create', 'Edit', 'Delete' => $this->{"can$check"}($member, $context), + default => parent::can($perm, $member, $context) + }; + } + + private function canPerformAction(string $canMethod, $member, $context = []) + { + // Allow extensions to override permission checks + $results = $this->extendedCan($canMethod, $member, $context); + if (isset($results)) { + return $results; + } + + // If we have an owner, rely on it to tell us what we can and can't do + $owner = $this->Owner(); + if ($owner && $owner->exists()) { + // Can delete or create links if you can edit its owner. + if ($canMethod === 'canCreate' || $canMethod === 'canDelete') { + $canMethod = 'canEdit'; + } + return $owner->$canMethod($member, $context); + } + + // Default to DataObject's permission checks + return parent::$canMethod($member, $context); + } + /** * Get all link types except the generic one * diff --git a/tests/php/Models/LinkTest.php b/tests/php/Models/LinkTest.php index 7ad54151..4ca853c3 100644 --- a/tests/php/Models/LinkTest.php +++ b/tests/php/Models/LinkTest.php @@ -515,4 +515,50 @@ public function testOwnerHasOne() $link->flushCache(false); $this->assertSame($owner->ID, $link->Owner()?->ID); } + + /** + * @dataProvider provideCanPermissions + */ + public function testCanPermissions(string $linkPermission, string $ownerPermission) + { + $link = $this->objFromFixture(SiteTreeLink::class, 'page-link-page-only'); + $owner = $link->Owner(); + $permissionName = substr($linkPermission, 3); + + $this->assertTrue($owner?->exists()); + + $owner->$ownerPermission = true; + $this->assertTrue($link->$linkPermission()); + $this->assertTrue($link->can($permissionName)); + + $owner->$ownerPermission = false; + $this->assertFalse($link->$linkPermission()); + $this->assertFalse($link->can($permissionName)); + } + + public function provideCanPermissions() + { + return [ + 'canView' => [ + 'linkPermission' => 'canView', + 'ownerPermission' => 'canView', + ], + 'canEdit' => [ + 'linkPermission' => 'canEdit', + 'ownerPermission' => 'canEdit', + ], + 'canDelete' => [ + 'linkPermission' => 'canDelete', + 'ownerPermission' => 'canEdit', + ], + ]; + } + + public function testCanCreate() + { + $link = $this->objFromFixture(SiteTreeLink::class, 'page-link-page-only'); + $this->logOut(); + $this->assertTrue($link->canCreate()); + $this->assertTrue($link->can('Create')); + } } diff --git a/tests/php/Models/LinkTest/LinkOwner.php b/tests/php/Models/LinkTest/LinkOwner.php index a9b9e02e..891cd642 100644 --- a/tests/php/Models/LinkTest/LinkOwner.php +++ b/tests/php/Models/LinkTest/LinkOwner.php @@ -15,4 +15,18 @@ class LinkOwner extends DataObject implements TestOnly private static array $has_many = [ 'LinkList' => Link::class . '.Owner', ]; + + // Allows us to toggle permissions easily within a unit test + public bool $canView = true; + public bool $canEdit = true; + + public function canView($member = null) + { + return $this->canView; + } + + public function canEdit($member = null) + { + return $this->canEdit; + } }