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 = "- $treeTitle"
- . $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..949410ba
--- /dev/null
+++ b/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_SubTree.ss
@@ -0,0 +1,20 @@
+<% if not $node.IsInDB %><%-- Only render root node if it's the true root --%>
+ - $rootTitle
+<% end_if %>
+<% if $limited %>
+
+<% else_if $children %>
+
+ <% loop $children %><% include SilverStripe\\CMS\\Controllers\\CMSMain_TreeNode %><% end_loop %>
+
+<% end_if %>
+<% 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..76ef1fe6
--- /dev/null
+++ b/templates/SilverStripe/CMS/Controllers/Includes/CMSMain_TreeNode.ss
@@ -0,0 +1,6 @@
+
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
-