Skip to content

Commit

Permalink
NEW Link Ownership (alternative)
Browse files Browse the repository at this point in the history
  • Loading branch information
GuySartorelli committed Oct 12, 2023
1 parent f984376 commit af09ab7
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 5 deletions.
7 changes: 7 additions & 0 deletions _config/config.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
---
Name: linkfield
---
# Adding this here for simplicity for testing the PR, but
# we'd have to make a determination as to whether this is
# automatically applied, or is documented as necessary
# for has_many links only.
SilverStripe\ORM\DataObject:
extensions:
- SilverStripe\LinkField\Extensions\DataObjectWithLinksExtension

SilverStripe\Admin\LeftAndMain:
extensions:
Expand Down
20 changes: 20 additions & 0 deletions src/Extensions/DataObjectWithLinksExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace SilverStripe\LinkField\Extensions;

use SilverStripe\Core\Extension;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\ORM\HasManyList;

/**
* This extension must be applied to any DataObject which has a has_many relation to the Link model.
*/
class DataObjectWithLinksExtension extends Extension
{
public function updateComponents(HasManyList &$list, string $relation)
{
if (is_a($list->dataClass(), Link::class, true)) {
$list = $list->filter('OwnerRelation', $relation);
}
}
}
4 changes: 4 additions & 0 deletions src/Form/JsonField.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public function saveInto(DataObjectInterface $record)
$dataValue = $this->dataValue();
$value = is_string($dataValue) ? $this->parseString($this->dataValue()) : $dataValue;

$value['OwnerID'] = $record->ID;
$value['OwnerClass'] = $record->ClassName;
$value['OwnerRelation'] = $this->getName();

if ($class = DataObject::getSchema()->hasOneComponent(get_class($record), $fieldname)) {
/** @var JsonData|DataObject $jsonDataObject */

Expand Down
13 changes: 12 additions & 1 deletion src/Form/MultiLinkField.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
use SilverStripe\ORM\SS_List;

/**
* Allows CMS users to edit a list of links.
* Allows CMS users to edit a list of links in a has_many relation.
* Explicitly doesn't support many_many.
*/
class MultiLinkField extends JsonField
{
Expand Down Expand Up @@ -88,19 +89,29 @@ public function saveInto(DataObjectInterface $record)

/** @var HasMany|Link[] $links */
if ($links = $record->$fieldname()) {
/** @var Link $linkDO */
foreach ($links as $linkDO) {
$linkData = $this->shiftLinkDataByID($value, $linkDO->ID);
if ($linkData) {
// @TODO move all the JSON stuff into the field. The model shouldn't care that
// the form field represents its data as JSON temporarily.
// We should just be calling $linkDO->update($data) here with the data already
// explicitly as an associative array.
// Also, I'm assuming this IS always an associative array, even though the Link
// model doesn't assume that.
$linkData['OwnerRelation'] = $this->getName();
$linkDO->setData($linkData);
$linkDO->write();
} else {
$linkDO->delete();
}
}

// Guy's note: I'm assuming these are explicitly new, and above is explicitly existing links
foreach ($value as $linkData) {
unset($linkData['ID']);
$linkDO = Link::create();
$linkData['OwnerRelation'] = $this->getName();
$linkDO = $linkDO->setData($linkData);
$links->add($linkDO);
$linkDO->write();
Expand Down
88 changes: 84 additions & 4 deletions src/Models/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
* A Link Data Object. This class should be a subclass, and 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 @@ -30,21 +33,23 @@ class Link extends DataObject implements JsonData, Type
private static $table_name = 'LinkField_Link';

private static array $db = [
'OwnerRelation' => 'Varchar',
'Title' => 'Varchar',
'OpenInNew' => 'Boolean',
];

private static $has_one = [
// See also the OwnerRelation field added in $db
'Owner' => DataObject::class
];

/**
* In-memory only property used to change link type
* This case is relevant for CMS edit form which doesn't use React driven UI
* This is a workaround as changing the ClassName directly is not fully supported in the GridField admin
*/
private ?string $linkType = null;

private static $has_one = [
'Owner' => DataObject::class
];

private static $icon = 'link';

public function defineLinkTypeRequirements()
Expand Down Expand Up @@ -292,4 +297,79 @@ protected function FallbackTitle(): string
{
return '';
}

/**
* This is entirely optional but is something we have the power to do now.
* We can also do checks like this in onBeforeWrite for example.
*/
public function Owner()
{
$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 = [])
{
$delegateToExistingMethods = [
'view',
'edit',
'create',
'delete',
];

$owner = $this->Owner();
if ($owner && $owner->exists() && !in_array(strtolower($perm), $delegateToExistingMethods)) {
return $owner->can($perm, $member, $context);
}

return parent::can($perm, $member, $context);
}

private function canPerformAction(string $canMethod, $member, $context = [])
{
$results = $this->extendedCan($canMethod, $member, $context);
if (isset($results)) {
return $results;
}

$owner = $this->Owner();
if ($owner && $owner->exists()) {
// Can delete or create links if you can edit its owner.
if ($canMethod === 'canView' ||$canMethod === 'canCreate' || $canMethod === 'canDelete') {
$canMethod = 'canEdit';
}
return $owner->$canMethod($member, $context);
}

return parent::$canMethod($member, $context);
}
}

0 comments on commit af09ab7

Please sign in to comment.