/** * Get a site tree HTML listing which displays the nodes under the given criteria. * * @param string $className The class of the root object * @param string $rootID The ID of the root object. If this is null then a complete tree will be * shown * @param string $childrenMethod The method to call to get the children of the tree. For example, * Children, AllChildrenIncludingDeleted, or AllHistoricalChildren * @param string $numChildrenMethod * @param callable $filterFunction * @param int $nodeCountThreshold * @return string Nested unordered list with links to each page */ public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $nodeCountThreshold = 30) { // Filter criteria $filter = $this->getSearchFilter(); // Default childrenMethod and numChildrenMethod if (!$childrenMethod) { $childrenMethod = $filter && $filter->getChildrenMethod() ? $filter->getChildrenMethod() : 'AllChildrenIncludingDeleted'; } if (!$numChildrenMethod) { $numChildrenMethod = 'numChildren'; if ($filter && $filter->getNumChildrenMethod()) { $numChildrenMethod = $filter->getNumChildrenMethod(); } } if (!$filterFunction && $filter) { $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); // Ensure current page is exposed if ($currentPage) { $obj->markToExpose($currentPage); } // NOTE: SiteTree/CMSMain coupling :-( if (class_exists('SilverStripe\\CMS\\Model\\SiteTree')) { SiteTree::prepopulate_permission_cache('CanEditType', $obj->markedNodeIDs(), 'SilverStripe\\CMS\\Model\\SiteTree::can_edit_multiple'); } // getChildrenAsUL is a flexible and complex way of traversing the tree $controller = $this; $recordController = $this->stat('tree_class') == 'SilverStripe\\CMS\\Model\\SiteTree' ? CMSPageEditController::singleton() : $this; $titleFn = function (&$child, $numChildrenMethod) use(&$controller, &$recordController, $filter) { $link = Controller::join_links($recordController->Link("show"), $child->ID); $node = LeftAndMain_TreeNode::create($child, $link, $controller->isCurrentPage($child), $numChildrenMethod, $filter); return $node->forTemplate(); }; // 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 = Config::inst()->get(Hierarchy::class, 'node_threshold_leaf'); if ($nodeThresholdLeaf && !$filterFunction) { $nodeCountCallback = function ($parent, $numChildren) use(&$controller, $className, $nodeThresholdLeaf) { if ($className !== 'SilverStripe\\CMS\\Model\\SiteTree' || !$parent->ID || $numChildren >= $nodeThresholdLeaf) { return null; } return sprintf('<ul><li class="readonly"><span class="item">' . '%s (<a href="%s" class="cms-panel-link" data-pjax-target="Content">%s</a>)' . '</span></li></ul>', _t('LeftAndMain.TooManyPages', 'Too many pages'), Controller::join_links($controller->LinkWithSearch($controller->Link()), ' ?view=list&ParentID=' . $parent->ID), _t('LeftAndMain.ShowAsList', 'show as list', 'Show large amount of pages in list instead of tree view')); }; } 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}()); } // 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) { $rootLink = $this->Link('show') . '/root'; // This lets us override the tree title with an extension if ($this->hasMethod('getCMSTreeTitle') && ($customTreeTitle = $this->getCMSTreeTitle())) { $treeTitle = $customTreeTitle; } elseif (class_exists('SilverStripe\\SiteConfig\\SiteConfig')) { $siteConfig = SiteConfig::current_site_config(); $treeTitle = Convert::raw2xml($siteConfig->Title); } else { $treeTitle = '...'; } $html = "<ul><li id=\"record-0\" data-id=\"0\" class=\"Root nodelete\"><strong>{$treeTitle}</strong>" . $html . "</li></ul>"; } return $html; }