Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Add an extension to dynamically generate edit URLs #1361

Merged
merged 2 commits into from
Sep 30, 2022

Conversation

GuySartorelli
Copy link
Member

@GuySartorelli GuySartorelli commented Sep 13, 2022

Currently getting an edit link for an object is a bit of a pain - you have to generate a new link based on the predictable gridfield or other admin edit paths. This is partly improved by #1360 - but that still doesn't resolve the problem for nested setups (e.g. object in a page's gridfield).

NOTE

CI won't go green until #1360 is merged in as the new tests rely on ModelAdmin having a getEditLinkForManagedDataObject() method

Related issue

/**
* Get a link to edit this page in the CMS.
*/
public function CMSEditLink(): string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File already has CMSEditLink method, would this be a problem when applied to e.g. Image or other class extending File?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of literally any reason someone would do that - but if they did, I'm pretty sure the trait implementation would take precedence.

* If the canonical edit owner is a has_one relation, the class on the other end
* of the relation must have a CMSEditLink() method.
*/
trait HasCanonicalEditLink
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was out for a bit and may have missed it, but do we have a naming convention for traits?

I'm a proponent of suffixing the name with the type of class/construct it is, e.g. SomethingTrait, PageController, FieldExtension etc. so it's immediately obvious what it is.

Suggested change
trait HasCanonicalEditLink
trait CanonicalEditLinkTrait

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have quite a few traits already - some are suffixed with Trait but the majority are not.
I think the fact that you declare it as a trait, and you use it, make it abundantly clear it's a trait.

Extension and Controller are a bit different because those aren't built-in language structure things like a trait (we don't say SiteTreeClass after all).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree we shouldn't suffix Trait on this one (even though I'd actually like it, I also like the Interface suffix), because most of the code base doesn't suffix it

I don't like the use of the word Canonical at all - it really should just be CMSEditLink

I had a look at different traits in the codebase to find a common naming convention, there isn't that much in the way of consistency.

The most common pattern I could find was just calling the trait the feature it implemented e.g.

  • trait ImageManiputlation
  • trait FlushScheduler
  • trait FormMessage

The next most consistent, though less common one was the Configurable / Extensible / Injectable - though those are less "concrete" concepts than just the other traits that just bolt a feature on

This makes a couple of obvious candidates

  • trait CMSEditLink
  • trait CMSEditLinkable (weird IMO)

I think CMSEditLink is the most non-horrible of the two

class MyClass
{
   use CMSEditLink;

And I get a CMSEditLink() method

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll rename the trait to CMSEditLink

@michalkleiner
Copy link
Contributor

I remember having issues not being able to get an edit link to objects in past so this would be a welcome addition, however I'm not sure if it should be a trait or somehow integrated more closely with DataObject itself or via an automatically applied extension to DataObject etc. What is the benefit of using the trait over an extension? Is this opt-in or would it be automatically applied?

@GuySartorelli
Copy link
Member Author

GuySartorelli commented Sep 14, 2022

What is the benefit of using the trait over an extension?

Less (if any) overhead is the main benefit - but mostly it just didn't really occur to me to make this as an extension.

Is this opt-in or would it be automatically applied?

It has to be opt-in. There's no way to know the canonical parent of some arbitrary model. How would we differentiate between the 12 has_one relations and the 3 model admins that all have edit links for some record?
These numbers are arbitrary but that's kinda my point. There's no way for us to determine, at a product-level, what some arbitrary project's model's canonical edit context is.

@GuySartorelli
Copy link
Member Author

I've renamed the trait to CMSEditLink and renamed the getEditLinkForObject() method to getEditLinkForDataObject() to match the change in #1360

@GuySartorelli
Copy link
Member Author

Steve and I have talked about this offline - I'll be converting this into an Extension and renaming getEditLinkForDataObject() to getEditLinkForManagedDataObject() so it's more intuitive.

@GuySartorelli GuySartorelli changed the title NEW Add a trait to dynamically generate edit URLs NEW Add a extension to dynamically generate edit URLs Sep 19, 2022
@GuySartorelli GuySartorelli changed the title NEW Add a extension to dynamically generate edit URLs NEW Add an extension to dynamically generate edit URLs Sep 19, 2022
@GuySartorelli GuySartorelli force-pushed the pulls/1/cmslink-trait branch 2 times, most recently from fb1a370 to 9cedf3d Compare September 26, 2022 22:24
if (is_numeric($k)) {
$models[$v] = ['dataClass' => $v, 'title' => singleton($v)->i18n_plural_name()];
unset($models[$k]);
// Custom tab url segment but no custom title
} elseif (is_a($v, DataObject::class, true)) {
$models[$k] = ['dataClass' => $v, 'title' => singleton($v)->i18n_plural_name()];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fix was needed for tests to go green.

@GuySartorelli GuySartorelli force-pushed the pulls/1/cmslink-trait branch 2 times, most recently from ab2b181 to fafd681 Compare September 26, 2022 22:35
Copy link
Member

@emteknetnz emteknetnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly naming stuff

Key thing that we missed when we merged #1360 was the method name ModelAdmin::getEditLinkForManagedDataObject - it should be getCMSEditLinkForManagedDataObject for consistency with the rest of the code base

A quick string search

vendor/silverstripe/**/*.php

"CMSEditLink(" - 33 results
"EditLink(" - 37 results

Of the "EditLink(" that weren't "CMSEditLink(":
"updateCMSEditLink(" - 1 result
"getFileEditLink(" - 3 results

So you'll also need to rename https://github.com/silverstripe/silverstripe-admin/blob/1/code/ModelAdmin.php#L230 in this PR as well

The other thing is the word "Canonical" is very confusing. Rename this to "CMSEdit" so that it's standard with everything else

I've left a bunch of recommended edits, though there's a large number of things that will need renaming e.g. the test files, docs, etc

&& $field->getConfig()->getComponentByType(GridFieldDetailForm::class);
}

private function constructLink(string $relation, int $id): string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private function constructLink(string $relation, int $id): string
private function constructCMSEditLink(string $relation, int $id): string

return Director::absoluteURL($relativeLink);
}

private function getLinkForRelation(array $componentConfig, DataObject $obj, string $reciprocalRelation, FieldList $fields): string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private function getLinkForRelation(array $componentConfig, DataObject $obj, string $reciprocalRelation, FieldList $fields): string
private function getCMSEditLinkForRelation(array $componentConfig, DataObject $obj, string $reciprocalRelation, FieldList $fields): string

* @throws LogicException if a link cannot be established
* e.g. if the object is not in a has_many relation or not edited inside a GridField.
*/
public function getEditLinkForManagedDataObject(DataObject $obj, string $reciprocalRelation): string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function getEditLinkForManagedDataObject(DataObject $obj, string $reciprocalRelation): string
public function getCMSEditLinkForManagedDataObject(DataObject $obj, string $reciprocalRelation): string

*/
class CMSEditLinkExtension extends Extension
{
private static string $canonical_edit_owner = '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static string $canonical_edit_owner = '';
private static string $cms_edit_owner = '';

*
* @return LeftAndMain|DataObject|null
*/
public function getCanonicalEditOwner()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function getCanonicalEditOwner()
public function getCMSEditOwner()

*/
public function CMSEditLink(): string
{
$owner = $this->owner->getCanonicalEditOwner();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$owner = $this->owner->getCanonicalEditOwner();
$owner = $this->owner->getCMSEditOwner();

return '';
}

if (!$owner->hasMethod('getEditLinkForManagedDataObject')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!$owner->hasMethod('getEditLinkForManagedDataObject')) {
if (!$owner->hasMethod('getCMSEditLinkForManagedDataObject')) {

}

if (!$owner->hasMethod('getEditLinkForManagedDataObject')) {
throw new LogicException('The canonical owner must implement getEditLinkForManagedDataObject()');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new LogicException('The canonical owner must implement getEditLinkForManagedDataObject()');
throw new LogicException('The canonical owner must implement getCMSEditLinkForManagedDataObject()');

if ($owner instanceof DataObject) {
$relativeLink = $owner->getEditLinkForManagedDataObject($this->owner, $this->owner->config()->get('canonical_edit_owner'));
} else {
$relativeLink = $owner->getEditLinkForManagedDataObject($this->owner);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$relativeLink = $owner->getEditLinkForManagedDataObject($this->owner);
$relativeLink = $owner->getCMSEditLinkForManagedDataObject($this->owner);


private function constructLink(string $relation, int $id): string
{
$ownerType = $this->owner->config()->get('canonical_edit_owner');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$ownerType = $this->owner->config()->get('canonical_edit_owner');
$ownerType = $this->owner->config()->get('cms_edit_owner');

@GuySartorelli
Copy link
Member Author

The other thing is the word "Canonical" is very confusing. Rename this to "CMSEdit" so that it's standard with everything else

"canonical" is correct in this context - e.g. the <link rel="canonical" href="https://www.example.com/page/" /> meta tag tells search engines what the canonical URL is for a page, in case the page can be accessed via multiple URLs.

But I'll make that change to avoid peer review ping pong and to match the other changes where "canonical" was already changed.

@GuySartorelli GuySartorelli force-pushed the pulls/1/cmslink-trait branch 2 times, most recently from 3d9d6a4 to 27c65cd Compare September 29, 2022 22:05
}

/**
* Get a link to edit this page in the CMS.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Get a link to edit this page in the CMS.
* Get a link to edit this DataObject in the CMS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

*
* Note that the cms edit owner must implement a getCMSEditLinkForManagedDataObject() method.
*
* If the cms edit owner is a has_one relation, the class on the other end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* If the cms edit owner is a has_one relation, the class on the other end
* If the cms_edit_owner is a has_one relation, the class on the other end

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

private static string $cms_edit_owner = '';

/**
* Get the admin or DataObject which owns this object for CMS editing purposes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Get the admin or DataObject which owns this object for CMS editing purposes.
* Get the ModelAdmin, LeftAndMain or DataObject which owns this object for CMS editing purposes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ModelAdmin is a LeftAndMain - and they're all admin. I'll make this change but it's super subjective.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@GuySartorelli GuySartorelli force-pushed the pulls/1/cmslink-trait branch 2 times, most recently from 43affee to 767db75 Compare September 29, 2022 23:07
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;

class BelongsToModelAdmin extends DataObject implements TestOnly
Copy link
Member

@emteknetnz emteknetnz Sep 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please rename this, it's not a "belongs_to" relationship, which is defined as the reciprocal of a has_one relationship when it's a 1-1 relations.

Also, it being suffixed with "ModelAdmin" confused me for a long time because I mistakenly thought it extended ModelAdmin

Even just calling this TestDataObject would be an improvement

Copy link
Member Author

@GuySartorelli GuySartorelli Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That wouldn't be an improvement, it's a lot less descriptive. I'll rename it to something that doesn't contain "belongsto" or end with "modeladmin". But given this is a test object I don't see that it matters all that much what it's called.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

'Nested' => NestedObject::class . '.Parent',
'AnotherArbitraryRelation' => NestedObject::class . '.AnotherOfTheSameClass',
'BasicNested' => BasicNestedObject::class,
'PolyMorphic' => PolymorphicNestedObject::class,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'PolyMorphic' => PolymorphicNestedObject::class,
'Polymorphic' => PolymorphicNestedObject::class,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@emteknetnz emteknetnz merged commit 769bbab into silverstripe:1 Sep 30, 2022
@emteknetnz emteknetnz deleted the pulls/1/cmslink-trait branch September 30, 2022 01:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants