Skip to content

Commit

Permalink
API Add new Owner relation for handling permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli committed Dec 5, 2023
1 parent 3c8edfd commit 6d1453d
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 6 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Page extends SiteTree
];

private static $has_many = [
'HasManyLinks' => Link::class
'HasManyLinks' => Link::class . '.Owner',
];

public function getCMSFields()
Expand All @@ -63,11 +63,9 @@ class Page extends SiteTree
}
```

Note that you also need to add a `has_one` relation on the `Link` model to match your `has_many` here. See [official docs about `has_many`](https://docs.silverstripe.org/en/developer_guides/model/relations/#has-many)

## Default title for each link type

By default, if the title for the link has not been set, then the default title will be used instead according to the type of link that is used. Default link is not stored in the database as link title. This value is used only when rendering page content.
By default, if the title for the link has not been set, then the default title will be used instead according to the type of link that is used. Default link is not stored in the database as link title. This value is used only when rendering page content.

The developer also can set his own default title value using an extension by using `updateDefaultLinkTitle` method for each link type class.

Expand Down
6 changes: 6 additions & 0 deletions src/Models/FileLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
use SilverStripe\Assets\File;
use SilverStripe\Forms\FieldList;

/**
* A link to a File in the CMS
*
* @property int $FileID
* @method File File()
*/
class FileLink extends Link
{
private static string $table_name = 'LinkField_FileLink';
Expand Down
91 changes: 89 additions & 2 deletions src/Models/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
use SilverStripe\Forms\RequiredFields;
use SilverStripe\LinkField\Type\Registry;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectSchema;
use SilverStripe\ORM\FieldType\DBHTMLText;

/**
* A Link Data Object. This class should be a subclass, and you should never directly interact with a plain Link
* instance
* A Link Data Object. This class should be treated as abstract. You should never directly interact with a plain Link
* instance.
*
* Note that links should be added via a has_one or has_many relation, NEVER a many_many relation. This is because
* some functionality such as the can* methods rely on having a single Owner.
*
* @property string $Title
* @property bool $OpenInNew
Expand All @@ -31,6 +35,17 @@ class Link extends DataObject
'OpenInNew' => 'Boolean',
];

private static array $has_one = [
// Note that this handles one-to-many relations AND one-to-one relations.
// Any has_one pointing at Link will be intentionally double handled - this allows us to use the owner
// for permission checks and to link back to the owner from reports, etc.
// See also the Owner method.
'Owner' => [
'class' => DataObject::class,
DataObjectSchema::HASONE_IS_MULTIRECIPROCAL => true,
],
];

/**
* In-memory only property used to change link type
* This case is relevant for CMS edit form which doesn't use React driven UI
Expand Down Expand Up @@ -277,6 +292,78 @@ public function getURL(): string
return '';
}

/**
* Get the owner of this link, if there is one.
*
* Returns null if the reciprocal relation is a has_one which no longer contains this link.
*/
public function Owner(): ?DataObject
{
$owner = $this->getComponent('Owner');
// Since the has_one is being stored in two places, double check the owner
// actually still owns this record. If not, return null.
$ownerRelationType = $owner->getRelationType($this->OwnerRelation);
if ($ownerRelationType === 'has_one') {
$idField = "{$this->OwnerRelation}ID";
if ($owner->$idField !== $this->ID) {
return null;
}
}
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, $context = [])
{
return $this->canPerformAction(__FUNCTION__, $member);
}

public function canCreate($member = null, $context = [])
{
return $this->canPerformAction(__FUNCTION__, $member);
}

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
*
Expand Down

0 comments on commit 6d1453d

Please sign in to comment.