From d75a3cb0e9db7b3f52bedec71043ebeefc969d1c Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 6 Apr 2017 17:38:15 +1200 Subject: [PATCH] API Update site tree hierarchy to use a MarkingSet and template --- code/Controllers/CMSMain.php | 197 ++++++++--------- code/Controllers/CMSTreeNode.php | 200 ------------------ code/Model/SiteTree.php | 11 +- code/Model/VirtualPage.php | 9 +- .../Controllers/Includes/CMSMain_SubTree.ss | 21 ++ .../Controllers/Includes/CMSMain_TreeNode.ss | 7 + .../CMS/Controllers/Includes/CMSTreeNode.ss | 4 - 7 files changed, 133 insertions(+), 316 deletions(-) delete mode 100644 code/Controllers/CMSTreeNode.php create mode 100644 templates/SilverStripe/CMS/Controllers/Includes/CMSMain_SubTree.ss create mode 100644 templates/SilverStripe/CMS/Controllers/Includes/CMSMain_TreeNode.ss delete mode 100644 templates/SilverStripe/CMS/Controllers/Includes/CMSTreeNode.ss diff --git a/code/Controllers/CMSMain.php b/code/Controllers/CMSMain.php index c00c004e..5d017da6 100644 --- a/code/Controllers/CMSMain.php +++ b/code/Controllers/CMSMain.php @@ -4,6 +4,7 @@ namespace SilverStripe\CMS\Controllers; use SilverStripe\Admin\AdminRootController; use SilverStripe\Admin\CMSBatchActionHandler; +use SilverStripe\Admin\LeftAndMain_SearchFilter; use SilverStripe\Admin\LeftAndMainFormRequestHandler; use SilverStripe\CMS\Model\VirtualPage; use SilverStripe\Forms\Tab; @@ -50,6 +51,7 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\HiddenClass; +use SilverStripe\ORM\Hierarchy\MarkedSet; use SilverStripe\ORM\SS_List; use SilverStripe\ORM\ValidationResult; use SilverStripe\SiteConfig\SiteConfig; @@ -429,132 +431,112 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr $filterFunction = null, $nodeCountThreshold = 30 ) { - - // Filter criteria + // Provide better defaults from filter $filter = $this->getSearchFilter(); - - // Default childrenMethod and numChildrenMethod + if ($filter) { if (!$childrenMethod) { - $childrenMethod = ($filter && $filter->getChildrenMethod()) - ? $filter->getChildrenMethod() - : 'AllChildrenIncludingDeleted'; + $childrenMethod = $filter->getChildrenMethod(); } - if (!$numChildrenMethod) { - $numChildrenMethod = 'numChildren'; - if ($filter && $filter->getNumChildrenMethod()) { $numChildrenMethod = $filter->getNumChildrenMethod(); } - } - if (!$filterFunction && $filter) { + if (!$filterFunction) { $filterFunction = function ($node) use ($filter) { return $filter->isPageIncluded($node); }; } - - // Get the tree root - $record = ($rootID) ? $this->getRecord($rootID) : null; - $obj = $record ? $record : singleton($className); - - // Get the current page - // NOTE: This *must* be fetched before markPartialTree() is called, as this - // causes the Hierarchy::$marked cache to be flushed (@see CMSMain::getRecord) - // which means that deleted pages stored in the marked tree would be removed - $currentPage = $this->currentPage(); - - // Mark the nodes of the tree to return - if ($filterFunction) { - $obj->setMarkingFilterFunction($filterFunction); } - $obj->markPartialTree($nodeCountThreshold, $this, $childrenMethod, $numChildrenMethod); + // Build set from node and begin marking + $record = ($rootID) ? $this->getRecord($rootID) : null; + $rootNode = $record ? $record : DataObject::singleton($className); + $markingSet = MarkedSet::create($rootNode, $childrenMethod, $numChildrenMethod, $nodeCountThreshold); + + // Set filter function + if ($filterFunction) { + $markingSet->setMarkingFilterFunction($filterFunction); + } + + // Mark tree from this node + $markingSet->markPartialTree(); // Ensure current page is exposed + $currentPage = $this->currentPage(); if ($currentPage) { - $obj->markToExpose($currentPage); + $markingSet->markToExpose($currentPage); } + // Pre-cache permissions SiteTree::prepopulate_permission_cache( 'CanEditType', - $obj->markedNodeIDs(), + $markingSet->markedNodeIDs(), [ SiteTree::class, 'can_edit_multiple'] ); - // getChildrenAsUL is a flexible and complex way of traversing the tree - $controller = $this; - $recordController = CMSPageEditController::singleton(); - $titleFn = function (&$child, $numChildrenMethod) use (&$controller, &$recordController, $filter) { - $link = Controller::join_links($recordController->Link("show"), $child->ID); - $node = CMSTreeNode::create($child, $link, $controller->isCurrentPage($child), $numChildrenMethod, $filter); - return $node->forTemplate(); - }; + // Render using full-subtree template + return $markingSet->renderChildren( + [ self::class . '_SubTree', 'type' => 'Includes' ], + $this->getTreeNodeCustomisations() + ); + } - // Limit the amount of nodes shown for performance reasons. - // Skip the check if we're filtering the tree, since its not clear how many children will - // match the filter criteria until they're queried (and matched up with previously marked nodes). - $nodeThresholdLeaf = SiteTree::config()->get('node_threshold_leaf'); - if ($nodeThresholdLeaf && !$filterFunction) { - $nodeCountCallback = function ($parent, $numChildren) use (&$controller, $nodeThresholdLeaf) { - if (!$parent->ID || $numChildren <= $nodeThresholdLeaf) { - return null; - } - return sprintf( - '', - _t('LeftAndMain.TooManyPages', 'Too many pages'), - Controller::join_links( - $controller->LinkWithSearch($controller->Link()), - '?view=listview&ParentID=' . $parent->ID + + /** + * Get callback to determine template customisations for nodes + * + * @return callable + */ + protected function getTreeNodeCustomisations() + { + $rootTitle = $this->getCMSTreeTitle(); + $linkWithSearch = $this->LinkWithSearch($this->Link()); + return function (SiteTree $node) use ($linkWithSearch, $rootTitle) { + return [ + 'listViewLink' => Controller::join_links( + $linkWithSearch, + '?view=listview&ParentID=' . $node->ID ), - _t( - 'LeftAndMain.ShowAsList', - 'show as list', - 'Show large amount of pages in list instead of tree view' - ) - ); + 'rootTitle' => $rootTitle, + 'extraClass' => $this->getTreeNodeClasses($node), + ]; }; - } else { - $nodeCountCallback = null; } - // If the amount of pages exceeds the node thresholds set, use the callback - $html = null; - if ($obj->ParentID && $nodeCountCallback) { - $html = $nodeCountCallback($obj, $obj->$numChildrenMethod()); + /** + * Get extra CSS classes for a page's tree node + * + * @param SiteTree $node + * @return string + */ + public function getTreeNodeClasses(SiteTree $node) + { + // Get classes from object + $classes = $node->CMSTreeClasses(); + + // Flag as current + if ($this->isCurrentPage($node)) { + $classes .= ' current'; } - // Otherwise return the actual tree (which might still filter leaf thresholds on children) - if (!$html) { - $html = $obj->getChildrenAsUL( - "", - $titleFn, - CMSPagesController::singleton(), - true, - $childrenMethod, - $numChildrenMethod, - $nodeCountThreshold, - $nodeCountCallback - ); - } - - // Wrap the root if needs be. - if (!$rootID) { - // This lets us override the tree title with an extension - if ($this->hasMethod('getCMSTreeTitle') && $customTreeTitle = $this->getCMSTreeTitle()) { - $treeTitle = $customTreeTitle; - } elseif (class_exists(SiteConfig::class)) { - $siteConfig = SiteConfig::current_site_config(); - $treeTitle = Convert::raw2xml($siteConfig->Title); - } else { - $treeTitle = '...'; + // Get status flag classes + $flags = $node->getStatusFlags(); + if ($flags) { + $statuses = array_keys($flags); + foreach ($statuses as $s) { + $classes .= ' status-' . $s; } - - $html = ""; } - return $html; + // Get additional filter classes + $filter = $this->getSearchFilter(); + if ($filter && ($filterClasses = $filter->getPageClasses($node))) { + if (is_array($filterClasses)) { + $filterClasses = implode(' ', $filterClasses); + } + $classes .= ' ' . $filterClasses; + } + + return trim($classes); } /** @@ -604,7 +586,13 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr if (!$record) { continue; // In case a page is no longer available } - $recordController = CMSPageEditController::singleton(); + + // Create marking set with sole marked root + $markingSet = MarkedSet::create($record); + $markingSet->setMarkingFilterFunction(function () { + return false; + }); + $markingSet->markUnexpanded($record); // Find the next & previous nodes, for proper positioning (Sort isn't good enough - it's not a raw offset) // TODO: These methods should really be in hierarchy - for a start it assumes Sort exists @@ -624,8 +612,11 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr ->first(); } - $link = Controller::join_links($recordController->Link("show"), $record->ID); - $html = CMSTreeNode::create($record, $link, $this->isCurrentPage($record))->forTemplate(). ''; + // Render using single node template + $html = $markingSet->renderChildren( + [ self::class . '_TreeNode', 'type' => 'Includes'], + $this->getTreeNodeCustomisations() + ); $data[$id] = array( 'html' => $html, @@ -2087,4 +2078,16 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr ) ); } + + /** + * Get title for root CMS node + * + * @return string + */ + protected function getCMSTreeTitle() + { + $rootTitle = SiteConfig::current_site_config()->Title; + $this->extend('updateCMSTreeTitle', $rootTitle); + return $rootTitle; + } } diff --git a/code/Controllers/CMSTreeNode.php b/code/Controllers/CMSTreeNode.php deleted file mode 100644 index cb127d4a..00000000 --- a/code/Controllers/CMSTreeNode.php +++ /dev/null @@ -1,200 +0,0 @@ -obj = $obj; - $this->link = $link; - $this->isCurrent = $isCurrent; - $this->numChildrenMethod = $numChildrenMethod; - $this->filter = $filter; - } - - /** - * Returns template, for further processing by {@link Hierarchy->getChildrenAsUL()}. - * Does not include closing tag to allow this method to inject its own children. - * - * @todo Remove hardcoded assumptions around returning an
  • , by implementing recursive tree node rendering - * - * @return string - */ - public function forTemplate() - { - $obj = $this->obj; - - return (string)SSViewer::execute_template( - [ 'type' => 'Includes', self::class ], - $obj, - array( - 'Classes' => $this->getClasses(), - 'Link' => $this->getLink(), - 'Title' => sprintf( - '(%s: %s) %s', - trim(_t('LeftAndMain.PAGETYPE', 'Page type'), " :"), - $obj->i18n_singular_name(), - $obj->Title - ), - ) - ); - } - - /** - * Determine the CSS classes to apply to this node - * - * @return string - */ - public function getClasses() - { - // Get classes from object - $classes = $this->obj->CMSTreeClasses($this->numChildrenMethod); - if ($this->isCurrent) { - $classes .= ' current'; - } - // Get status flag classes - $flags = $this->obj->hasMethod('getStatusFlags') - ? $this->obj->getStatusFlags() - : false; - if ($flags) { - $statuses = array_keys($flags); - foreach ($statuses as $s) { - $classes .= ' status-' . $s; - } - } - // Get additional filter classes - if ($this->filter && ($filterClasses = $this->filter->getPageClasses($this->obj))) { - if (is_array($filterClasses)) { - $filterClasses = implode(' ', $filterClasses); - } - $classes .= ' ' . $filterClasses; - } - return $classes ?: ''; - } - - /** - * Get page backing this node - * - * @return SiteTree - */ - public function getObj() - { - return $this->obj; - } - - /** - * Set object backing this node - * - * @param SiteTree $obj - * @return $this - */ - public function setObj($obj) - { - $this->obj = $obj; - return $this; - } - - /** - * Get link to this node - * - * @return string - */ - public function getLink() - { - return $this->link; - } - - /** - * Set link to this node - * - * @param string $link - * @return $this - */ - public function setLink($link) - { - $this->link = $link; - return $this; - } - - /** - * Check if this is the currently selected node - * - * @return bool - */ - public function getIsCurrent() - { - return $this->isCurrent; - } - - /** - * Set this node to current, or not current - * - * @param bool $bool - * @return $this - */ - public function setIsCurrent($bool) - { - $this->isCurrent = $bool; - return $this; - } -} diff --git a/code/Model/SiteTree.php b/code/Model/SiteTree.php index 4e300df8..409823a8 100755 --- a/code/Model/SiteTree.php +++ b/code/Model/SiteTree.php @@ -46,6 +46,7 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; use SilverStripe\ORM\HiddenClass; use SilverStripe\ORM\Hierarchy\Hierarchy; +use SilverStripe\ORM\Hierarchy\MarkedSet; use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\ValidationResult; use SilverStripe\Versioned\Versioned; @@ -2966,10 +2967,9 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi /** * Return the CSS classes to apply to this node in the CMS tree. * - * @param string $numChildrenMethod * @return string */ - public function CMSTreeClasses($numChildrenMethod = "numChildren") + public function CMSTreeClasses() { $classes = sprintf('class-%s', static::class); if ($this->HasBrokenFile || $this->HasBrokenLink) { @@ -2992,13 +2992,6 @@ class SiteTree extends DataObject implements PermissionProvider, i18nEntityProvi $classes .= " notinmenu"; } - //TODO: Add integration - /* - if($this->hasExtension('Translatable') && $controller->Locale != Translatable::default_locale() && !$this->isTranslation()) - $classes .= " untranslated "; - */ - $classes .= $this->markingClasses($numChildrenMethod); - return $classes; } diff --git a/code/Model/VirtualPage.php b/code/Model/VirtualPage.php index c78945cb..b23833de 100644 --- a/code/Model/VirtualPage.php +++ b/code/Model/VirtualPage.php @@ -7,6 +7,7 @@ use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\ReadonlyTransformation; use SilverStripe\Forms\TreeDropdownField; use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\Hierarchy\MarkedSet; use SilverStripe\ORM\ValidationResult; use SilverStripe\Versioned\Versioned; use SilverStripe\Security\Member; @@ -354,13 +355,9 @@ class VirtualPage extends Page $this->ImageTracking()->setByIDList($this->CopyContentFrom()->ImageTracking()->column('ID')); } - /** - * @param string $numChildrenMethod - * @return string - */ - public function CMSTreeClasses($numChildrenMethod = "numChildren") + public function CMSTreeClasses() { - return parent::CMSTreeClasses($numChildrenMethod) . ' VirtualPage-' . $this->CopyContentFrom()->ClassName; + return parent::CMSTreeClasses() . ' VirtualPage-' . $this->CopyContentFrom()->ClassName; } /** diff --git a/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_SubTree.ss b/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_SubTree.ss new file mode 100644 index 00000000..e73bef2d --- /dev/null +++ b/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_SubTree.ss @@ -0,0 +1,21 @@ + +<% if not $node.IsInDB %> + +<% end_if %> diff --git a/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_TreeNode.ss b/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_TreeNode.ss new file mode 100644 index 00000000..c091730e --- /dev/null +++ b/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_TreeNode.ss @@ -0,0 +1,7 @@ +
  • +   +   + {$node.TreeTitle} + + $SubTree +
  • diff --git a/templates/SilverStripe/CMS/Controllers/Includes/CMSTreeNode.ss b/templates/SilverStripe/CMS/Controllers/Includes/CMSTreeNode.ss deleted file mode 100644 index 51b8b874..00000000 --- a/templates/SilverStripe/CMS/Controllers/Includes/CMSTreeNode.ss +++ /dev/null @@ -1,4 +0,0 @@ -
  •   -   - $TreeTitle -