-
Notifications
You must be signed in to change notification settings - Fork 5
Update to Silverstripe 6 #20
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,101 +3,82 @@ | |
| namespace Nightjar\Slug; | ||
|
|
||
| use InvalidArgumentException; | ||
| use UnexpectedValueException; | ||
| use SilverStripe\ORM\DataObject; | ||
| use SilverStripe\Forms\FieldList; | ||
| use SilverStripe\ORM\DataExtension; | ||
| use Override; | ||
| use SilverStripe\Control\Controller; | ||
| use SilverStripe\Core\Extension; | ||
| use SilverStripe\Forms\FieldList; | ||
| use SilverStripe\ORM\DataObject; | ||
| use SilverStripe\View\Parsers\URLSegmentFilter; | ||
| use UnexpectedValueException; | ||
|
|
||
| /** | ||
| * Adds a 'url slug' property to DataObject classes, in order for them to be able to be loaded via a URL | ||
| * through a{@see SilverStripe\Control\Controller}. The typical use case for this is to view records related | ||
| * to a {@see Page}, to be able to have a controller action that doesn't need to reference the ID ofthe record. | ||
| * through a{@see \SilverStripe\Control\Controller}. The typical use case for this is to view records related | ||
| * to a {@see Page}, to be able to have a controller action that doesn't need to reference the ID of the record. | ||
| * E.g. /products/nice-jacket - where this is /pageType/relatedObject-notAPage | ||
| * | ||
| * To simplify the controller section of this purpose {@see SlugHandler} | ||
| */ | ||
| class Slug extends DataExtension | ||
| class Slug extends Extension | ||
| { | ||
| /** | ||
| * Used to set {@see $active} to inactive, or 'link' | ||
| */ | ||
| const ACTIVE_NONE = null; | ||
| const null ACTIVE_NONE = null; | ||
|
|
||
| /** | ||
| * Used to set and check {@see $active} as a section (ancestor of 'current') | ||
| */ | ||
| const ACTIVE_SECTION = false; | ||
| const false ACTIVE_SECTION = false; | ||
|
|
||
| /** | ||
| * Used to set and check {@see $active} as the current object active in a request | ||
| */ | ||
| const ACTIVE_CURRENT = true; | ||
| const true ACTIVE_CURRENT = true; | ||
|
|
||
| private static $db = [ | ||
| private static array $db = [ | ||
| 'URLSlug' => 'Varchar(255)', | ||
| ]; | ||
|
|
||
| private static $indexes = [ | ||
| private static array $indexes = [ | ||
| 'URLSlug' => true, | ||
| ]; | ||
|
|
||
| /** | ||
| * The field on the owner that we should take the value from in order to generate a slug | ||
| * | ||
| * @var string | ||
| */ | ||
| protected $fieldToSlug; | ||
|
|
||
| /** | ||
| * If we should restrict the slugging uniqueness to a certain subset of the owner class, | ||
| * this will be the name of the relation to filter by to detect uniqueness | ||
| * | ||
| * @var string|null | ||
| */ | ||
| protected $relationName; | ||
|
|
||
| /** | ||
| * Whether or not we should update the URLSlug field when the field to slug changes | ||
| * | ||
| * @var boolean | ||
| */ | ||
| protected $enforceParity; | ||
|
|
||
| /** | ||
| * The owner has been accessed via a route involving the URLSlug | ||
| * Tri state; current, section, none | ||
| * | ||
| * @var null|boolean {@see setSlugActive} | ||
| */ | ||
| protected $active = null; | ||
| protected ?bool $active = null; | ||
|
|
||
| /** | ||
| * Apply extension with configurable defaults | ||
| * | ||
| * @param string $fieldToSlug The field on the owner to base the URL Slug from - defaults to 'Title' | ||
| * @param string $relationName Optional name of the has_one relationship to the owner's parent class/Page | ||
| * @param string $fieldToSlug The field on the owner that we should take the value from to generate a slug - defaults to 'Title' | ||
| * @param string $relationName Optional name of the has_one relationship to the owner's parent class/Page | ||
| * @param boolean $enforceParity true to alter the URLSlug whenever the $fieldToSlug changes value (default: false) | ||
| */ | ||
| public function __construct($fieldToSlug = 'Title', $relationName = null, $enforceParity = false) | ||
| { | ||
| public function __construct( | ||
| protected $fieldToSlug = 'Title', | ||
| /** | ||
| * If we should restrict the slugging uniqueness to a certain subset of the owner class, | ||
| * this will be the name of the relation to filter by to detect uniqueness | ||
| */ | ||
| protected $relationName = null, | ||
| protected $enforceParity = false | ||
| ) { | ||
| parent::__construct(); | ||
| $this->fieldToSlug = $fieldToSlug; | ||
| $this->relationName = $relationName; | ||
| $this->enforceParity = $enforceParity; | ||
| } | ||
|
|
||
| #[Override] | ||
| public function setOwner($owner) | ||
| { | ||
| // parent method is set in a try, with a finally to clearOwner - so it is important we set it first, | ||
| // otherwise our InvalidArgumentException will be swallowed by a BadMethodCallException! | ||
| parent::setOwner($owner); | ||
|
|
||
| // throw an exception if the $relationName is invalid or not has_one | ||
| if ($this->relationName) { | ||
| $ownerClass = get_class($owner); | ||
| $valid = DataObject::getSchema()->hasOneComponent($ownerClass, $this->relationName); | ||
| if ($this->relationName && $owner) { | ||
| $ownerClass = $owner::class; | ||
| $valid = DataObject::getSchema()?->hasOneComponent($ownerClass, $this->relationName); | ||
| if (!$valid) { | ||
| throw new InvalidArgumentException("$this->relationName is an invalid has_one on $ownerClass"); | ||
| } | ||
|
|
@@ -134,7 +115,7 @@ public function setSlugActive($active) | |
| /** | ||
| * Generate a url slug segment | ||
| * | ||
| * @param boolean $forceRegeneration | ||
| * @param boolean $forceRegeneration | ||
| * @return string | ||
| */ | ||
| public function getSlug($forceRegeneration = false) | ||
|
|
@@ -166,13 +147,13 @@ public function onBeforeWrite() | |
| if ($updateSlug) { | ||
| $owner->URLSlug = $this->getSlug($this->enforceParity); | ||
|
|
||
| $collisionList = DataObject::get(get_class($owner))->exclude('ID', $owner->ID); | ||
| $collisionList = DataObject::get($owner::class)->exclude(['ID' => $owner->ID]); | ||
| $filter = ['URLSlug' => $owner->URLSlug]; | ||
| if ($this->relationName) { | ||
| $parentIDField = $this->relationName . 'ID'; | ||
| $filter[$parentIDField] = $owner->$parentIDField; | ||
| // Also handle polymorphic relationships | ||
| $parentClassName = DataObject::getSchema()->hasOneComponent(get_class($owner), $this->relationName); | ||
| $parentClassName = DataObject::getSchema()->hasOneComponent($owner::class, $this->relationName); | ||
| if ($parentClassName === DataObject::class) { | ||
| $parentClassField = $this->relationName . 'Class'; | ||
| $filter[$parentClassField] = $owner->$parentClassField; | ||
|
|
@@ -181,7 +162,7 @@ public function onBeforeWrite() | |
|
|
||
| $count = 1; | ||
| while ($collisionList->filter($filter)->exists()) { | ||
| $owner->URLSlug = $owner->URLSlug . $count++; | ||
| $owner->URLSlug .= $count++; | ||
| $filter['URLSlug'] = $owner->URLSlug; | ||
| } | ||
| } elseif ($slugHasChanged) { | ||
|
|
@@ -218,11 +199,11 @@ public function Link($action = null) | |
| $link = null; | ||
| $owner = $this->getOwner(); | ||
| $action = ($action) ? Controller::join_links($owner->URLSlug, $action) : $owner->URLSlug; | ||
|
|
||
| $relationName = $this->relationName; | ||
| if ($relationName && ($parent = $owner->$relationName()) && $parent->hasMethod('Link')) { | ||
| $link = $parent->Link($action); | ||
| } elseif (Controller::has_curr()) { | ||
| } elseif (Controller::curr() === null) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the exact opposite of what we want 😉 Since |
||
| // Quite the assumption, but sufficient in most cases. | ||
| $link = Controller::curr()->Link($action); | ||
| } | ||
|
|
@@ -282,10 +263,12 @@ public function LinkingMode() | |
| { | ||
| if ($this->isCurrent()) { | ||
| return 'current'; | ||
| } elseif ($this->isSection()) { | ||
| } | ||
|
|
||
| if ($this->isSection()) { | ||
| return 'section'; | ||
| } else { | ||
| return 'link'; | ||
| } | ||
|
|
||
| return 'link'; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,8 @@ | |
|
|
||
| namespace Nightjar\Slug\Tests; | ||
|
|
||
| use Override; | ||
| use Nightjar\Slug\Slug; | ||
| use InvalidArgumentException; | ||
| use SilverStripe\Control\Director; | ||
| use SilverStripe\Core\Config\Config; | ||
| use SilverStripe\Dev\FunctionalTest; | ||
|
|
@@ -33,20 +33,18 @@ class SlugHandlerTest extends FunctionalTest | |
| * fixture classes, and here we are also testing defining a service to apply the extension works. | ||
| * This is important for e.g. backwards compatiblity aliases | ||
| */ | ||
| #[Override] | ||
| public static function getExtraDataObjects() | ||
| { | ||
| $config = Config::modify(); | ||
| $config->set(Injector::class, 'JournalistSlug', [ | ||
| 'class' => Slug::class, | ||
| 'constructor' => ['Name', 'NewsPages'], | ||
| $config->set(Journalist::class, 'extensions', [ | ||
| Slug::class . '("Name")' | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The change also neglects to carry over the second parameter to the constructor. Am I correct in recalling that the test didn't pass previously? 😬
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. afair the second param didn't pass before, so it was useless. |
||
| ]); | ||
| $config->merge(Journalist::class, 'extensions', ['JournalistSlug']); | ||
|
|
||
| // Do what we actually came here for | ||
| return parent::getExtraDataObjects(); | ||
| } | ||
|
|
||
| protected function setUp(): void | ||
| protected function setUp(): void | ||
| { | ||
| parent::setUp(); | ||
| $config = Config::modify(); | ||
|
|
@@ -73,21 +71,21 @@ public function testRequestingSlugs() | |
| $news->Journalists()->add($jim); | ||
|
|
||
| $output = $this->get('news/'); | ||
| $this->assertEquals('Index ok', $output->getBody()); | ||
| $this->assertEquals('Index ok', trim((string) $output->getBody())); | ||
| $output = $this->get('news/nonsense/first-news'); | ||
| $this->assertEquals(404, $output->getStatusCode()); | ||
|
|
||
| $output = $this->get('news/stories/'); | ||
| $this->assertEquals(404, $output->getStatusCode()); | ||
| $output = $this->get('news/stories/first-news'); | ||
| $this->assertEquals('First News', $output->getBody()); | ||
| $this->assertEquals('First News', trim((string) $output->getBody())); | ||
| $output = $this->get('news/first-news'); | ||
| $this->assertEquals(404, $output->getStatusCode()); | ||
|
|
||
| $output = $this->get('news/contributors/'); | ||
| $this->assertEquals(404, $output->getStatusCode()); | ||
| $output = $this->get('news/contributors/jimbo-the-journo'); | ||
| $this->assertEquals('Jimbo the Journo', $output->getBody()); | ||
| $this->assertEquals('Jimbo the Journo', trim((string) $output->getBody())); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was there an issue with the output? The Perhaps I misunderstand and the behaviour of Same for the other 2 calls above. |
||
| $output = $this->get('news/jimbo-the-journo'); | ||
| $this->assertEquals(404, $output->getStatusCode()); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated because this is just syntactic change, but it occurs to me we might end up with
slug12345instead ofslug5😂 - should probably write a test at some point (no need to do it for this PR though).