Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0bf069c
commit 768daa0
Showing
12 changed files
with
1,068 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
<?php | ||
|
||
namespace DNADesign\Elemental\TopPage; | ||
|
||
use DNADesign\Elemental\Models\BaseElement; | ||
use DNADesign\Elemental\Models\ElementalArea; | ||
use Page; | ||
use SilverStripe\ORM\DataExtension as BaseDataExtension; | ||
use SilverStripe\ORM\DataObject; | ||
use SilverStripe\ORM\ValidationException; | ||
use SilverStripe\Versioned\Versioned; | ||
|
||
/** | ||
* Class DataExtension | ||
* | ||
* Top page data cache for improved performance | ||
* intended owners of this extension are @see BaseElement and @see ElementalArea | ||
* applying this extension to just one of these owners will not hinder top page functionality | ||
* but the performance gain will be smaller | ||
* it is recommended to apply this extension to BaseElement and for setups with deeper block nesting | ||
* it is recommended to cover ElementalArea as well | ||
* | ||
* @property int $TopPageID | ||
* @method Page TopPage() | ||
* @property BaseElement|ElementalArea|$this $owner | ||
* @package DNADesign\Elemental\TopPage | ||
*/ | ||
class DataExtension extends BaseDataExtension | ||
{ | ||
/** | ||
* @config | ||
* @var array | ||
*/ | ||
private static $has_one = [ | ||
'TopPage' => Page::class, | ||
]; | ||
|
||
/** | ||
* @config | ||
* @var array | ||
*/ | ||
private static $indexes = [ | ||
'TopPageID' => true, | ||
]; | ||
|
||
/** | ||
* @var bool | ||
*/ | ||
private $skipTopPageUpdate = false; | ||
|
||
/** | ||
* Exension point in @see DataObject::onAfterWrite() | ||
*/ | ||
public function onAfterWrite(): void | ||
{ | ||
$this->setTopPage(); | ||
} | ||
|
||
/** | ||
* Exension point in @see DataObject::duplicate() | ||
*/ | ||
public function onBeforeDuplicate(): void | ||
{ | ||
$this->clearTopPage(); | ||
} | ||
|
||
/** | ||
* Exension point in @see DataObject::duplicate() | ||
*/ | ||
public function onAfterDuplicate(): void | ||
{ | ||
$this->updateTopPage(); | ||
} | ||
|
||
/** | ||
* Find top level page of a block or elemental area | ||
* this is very useful in case blocks are deeply nested | ||
* | ||
* for example: | ||
* page -> elemental area -> block -> elemental area -> block | ||
* | ||
* this lookup is very performant as is safe to use in a template as well | ||
* | ||
* @return Page|null | ||
* @throws ValidationException | ||
*/ | ||
public function getTopPage(): ?Page | ||
{ | ||
$list = [$this->owner]; | ||
|
||
while (count($list) > 0) { | ||
/** @var DataObject|DataExtension $item */ | ||
$item = array_shift($list); | ||
|
||
if ($item instanceof Page) { | ||
// trivial case | ||
return $item; | ||
} | ||
|
||
if ($item->hasExtension(DataExtension::class) && $item->TopPageID > 0) { | ||
// top page is stored inside data object - just fetch it via cached call | ||
$page = Page::get_by_id($item->TopPageID); | ||
|
||
if ($page !== null && $page->exists()) { | ||
return $page; | ||
} | ||
} | ||
|
||
if ($item instanceof BaseElement) { | ||
// parent lookup via block | ||
$parent = $item->Parent(); | ||
|
||
if ($parent !== null && $parent->exists()) { | ||
array_push($list, $parent); | ||
} | ||
|
||
continue; | ||
} | ||
|
||
if ($item instanceof ElementalArea) { | ||
// parent lookup via elemental area | ||
$parent = $item->getOwnerPage(); | ||
|
||
if ($parent !== null && $parent->exists()) { | ||
array_push($list, $parent); | ||
} | ||
|
||
continue; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* @param Page|null $page | ||
* @throws ValidationException | ||
*/ | ||
public function setTopPage(?Page $page = null): void | ||
{ | ||
if ($this->skipTopPageUpdate) { | ||
return; | ||
} | ||
|
||
/** @var BaseElement|ElementalArea|Versioned|DataExtension $owner */ | ||
$owner = $this->owner; | ||
|
||
if (!$owner->hasExtension(DataExtension::class)) { | ||
return; | ||
} | ||
|
||
if ($owner->TopPageID > 0) { | ||
return; | ||
} | ||
|
||
$page = $page ?? $owner->getTopPage(); | ||
|
||
if ($page === null) { | ||
return; | ||
} | ||
|
||
// set the page to properties in case this object is re-used later | ||
$this->assignTopPage($page); | ||
|
||
if ($owner->hasExtension(Versioned::class)) { | ||
$owner->writeWithoutVersion(); | ||
|
||
return; | ||
} | ||
|
||
$owner->write(); | ||
} | ||
|
||
/** | ||
* Use this to wrap any code which is supposed to run without doing any top page updates | ||
* | ||
* @param callable $callback | ||
* @return mixed | ||
*/ | ||
public function withoutTopPageUpdate(callable $callback) | ||
{ | ||
$this->skipTopPageUpdate = true; | ||
|
||
try { | ||
return $callback(); | ||
} finally { | ||
$this->skipTopPageUpdate = false; | ||
} | ||
} | ||
|
||
/** | ||
* Register the object for top page update | ||
* this is a little bit roundabout way to do it, but it's necessary because when cloned object is written | ||
* the relations are not yet written so it's impossible to do a parent lookup at that time | ||
*/ | ||
protected function updateTopPage(): void | ||
{ | ||
/** @var SiteTreeExtension $extension */ | ||
$extension = singleton(SiteTreeExtension::class); | ||
$extension->addDuplicatedObject($this->owner); | ||
} | ||
|
||
protected function assignTopPage(Page $page): void | ||
{ | ||
$this->owner->TopPageID = (int) $page->ID; | ||
} | ||
|
||
/** | ||
* Clears top page relation, this is useful when duplicating object as the new object doesn't necessarily | ||
* belong to the original page | ||
*/ | ||
protected function clearTopPage(): void | ||
{ | ||
$this->owner->TopPageID = 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
namespace DNADesign\Elemental\TopPage; | ||
|
||
use DNADesign\Elemental\Models\BaseElement; | ||
use DNADesign\Elemental\Models\ElementalArea; | ||
use Page; | ||
use TractorCow\Fluent\State\FluentState; | ||
|
||
/** | ||
* Class FluentExtension | ||
* | ||
* Use this extension in case you use the Fluent module (https://github.com/tractorcow-farm/silverstripe-fluent) | ||
* for page localisation | ||
* this will keep track of the locale the nested data object is stored in | ||
* | ||
* @property string $TopPageLocale | ||
* @property BaseElement|ElementalArea|$this $owner | ||
* @package DNADesign\Elemental\TopPage | ||
*/ | ||
class FluentExtension extends DataExtension | ||
{ | ||
/** | ||
* @var array | ||
*/ | ||
private static $db = [ | ||
'TopPageLocale' => 'Varchar', | ||
]; | ||
|
||
protected function assignTopPage(Page $page): void | ||
{ | ||
parent::assignTopPage($page); | ||
|
||
$this->owner->TopPageLocale = FluentState::singleton()->getLocale(); | ||
} | ||
|
||
protected function clearTopPage(): void | ||
{ | ||
parent::clearTopPage(); | ||
|
||
$this->owner->TopPageLocale = null; | ||
} | ||
} |
Oops, something went wrong.