/**
     * 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;
    }
 public function setUp()
 {
     //nest config and injector for each test so they are effectively sandboxed per test
     Config::nest();
     Injector::nest();
     $this->originalReadingMode = Versioned::get_reading_mode();
     // We cannot run the tests on this abstract class.
     if (get_class($this) == "SapphireTest") {
         $this->markTestSkipped(sprintf('Skipping %s ', get_class($this)));
         return;
     }
     // Mark test as being run
     $this->originalIsRunningTest = self::$is_running_test;
     self::$is_running_test = true;
     // i18n needs to be set to the defaults or tests fail
     i18n::set_locale(Config::inst()->get('i18n', 'default_locale'));
     i18n::config()->date_format = null;
     i18n::config()->time_format = null;
     // Set default timezone consistently to avoid NZ-specific dependencies
     date_default_timezone_set('UTC');
     // Remove password validation
     $this->originalMemberPasswordValidator = Member::password_validator();
     $this->originalRequirements = Requirements::backend();
     Member::set_password_validator(null);
     Config::inst()->update('Cookie', 'report_errors', false);
     if (class_exists('SilverStripe\\CMS\\Controllers\\RootURLController')) {
         RootURLController::reset();
     }
     if (class_exists('Translatable')) {
         Translatable::reset();
     }
     Versioned::reset();
     DataObject::reset();
     if (class_exists('SilverStripe\\CMS\\Model\\SiteTree')) {
         SiteTree::reset();
     }
     Hierarchy::reset();
     if (Controller::has_curr()) {
         Controller::curr()->setSession(Injector::inst()->create('Session', array()));
     }
     Security::$database_is_ready = null;
     // Add controller-name auto-routing
     Config::inst()->update('Director', 'rules', array('$Controller//$Action/$ID/$OtherID' => '*'));
     $fixtureFile = static::get_fixture_file();
     $prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_';
     // Todo: this could be a special test model
     $this->model = DataModel::inst();
     // Set up fixture
     if ($fixtureFile || $this->usesDatabase) {
         if (!self::using_temp_db()) {
             self::create_temp_db();
         }
         singleton('SilverStripe\\ORM\\DataObject')->flushCache();
         self::empty_temp_db();
         foreach ($this->requireDefaultRecordsFrom as $className) {
             $instance = singleton($className);
             if (method_exists($instance, 'requireDefaultRecords')) {
                 $instance->requireDefaultRecords();
             }
             if (method_exists($instance, 'augmentDefaultRecords')) {
                 $instance->augmentDefaultRecords();
             }
         }
         if ($fixtureFile) {
             $pathForClass = $this->getCurrentAbsolutePath();
             $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : array($fixtureFile);
             $i = 0;
             foreach ($fixtureFiles as $fixtureFilePath) {
                 // Support fixture paths relative to the test class, rather than relative to webroot
                 // String checking is faster than file_exists() calls.
                 $isRelativeToFile = strpos('/', $fixtureFilePath) === false || preg_match('/^\\.\\./', $fixtureFilePath);
                 if ($isRelativeToFile) {
                     $resolvedPath = realpath($pathForClass . '/' . $fixtureFilePath);
                     if ($resolvedPath) {
                         $fixtureFilePath = $resolvedPath;
                     }
                 }
                 $fixture = Injector::inst()->create('YamlFixture', $fixtureFilePath);
                 $fixture->writeInto($this->getFixtureFactory());
                 $this->fixtures[] = $fixture;
                 // backwards compatibility: Load first fixture into $this->fixture
                 if ($i == 0) {
                     $this->fixture = $fixture;
                 }
                 $i++;
             }
         }
         $this->logInWithPermission("ADMIN");
     }
     // Preserve memory settings
     $this->originalMemoryLimit = ini_get('memory_limit');
     // turn off template debugging
     Config::inst()->update('SSViewer', 'source_file_comments', false);
     // Clear requirements
     Requirements::clear();
     // Set up email
     $this->originalMailer = Email::mailer();
     $this->mailer = new TestMailer();
     Injector::inst()->registerService($this->mailer, 'Mailer');
     Config::inst()->remove('Email', 'send_all_emails_to');
 }
 public function setUp()
 {
     //nest config and injector for each test so they are effectively sandboxed per test
     Config::nest();
     Injector::nest();
     $this->originalReadingMode = Versioned::get_reading_mode();
     // We cannot run the tests on this abstract class.
     if (get_class($this) == __CLASS__) {
         $this->markTestSkipped(sprintf('Skipping %s ', get_class($this)));
         return;
     }
     // Mark test as being run
     $this->originalIsRunningTest = self::$is_running_test;
     self::$is_running_test = true;
     // i18n needs to be set to the defaults or tests fail
     i18n::set_locale(i18n::config()->get('default_locale'));
     i18n::config()->date_format = null;
     i18n::config()->time_format = null;
     // Set default timezone consistently to avoid NZ-specific dependencies
     date_default_timezone_set('UTC');
     // Remove password validation
     $this->originalMemberPasswordValidator = Member::password_validator();
     $this->originalRequirements = Requirements::backend();
     Member::set_password_validator(null);
     Cookie::config()->update('report_errors', false);
     if (class_exists('SilverStripe\\CMS\\Controllers\\RootURLController')) {
         RootURLController::reset();
     }
     if (class_exists('Translatable')) {
         Translatable::reset();
     }
     Versioned::reset();
     DataObject::reset();
     if (class_exists('SilverStripe\\CMS\\Model\\SiteTree')) {
         SiteTree::reset();
     }
     Hierarchy::reset();
     if (Controller::has_curr()) {
         Controller::curr()->setSession(Session::create(array()));
     }
     Security::$database_is_ready = null;
     // Add controller-name auto-routing
     // @todo Fix to work with namespaced controllers
     Director::config()->update('rules', array('$Controller//$Action/$ID/$OtherID' => '*'));
     $fixtureFiles = $this->getFixturePaths();
     // Todo: this could be a special test model
     $this->model = DataModel::inst();
     // Set up fixture
     if ($fixtureFiles || $this->usesDatabase) {
         if (!self::using_temp_db()) {
             self::create_temp_db();
         }
         DataObject::singleton()->flushCache();
         self::empty_temp_db();
         foreach ($this->requireDefaultRecordsFrom as $className) {
             $instance = singleton($className);
             if (method_exists($instance, 'requireDefaultRecords')) {
                 $instance->requireDefaultRecords();
             }
             if (method_exists($instance, 'augmentDefaultRecords')) {
                 $instance->augmentDefaultRecords();
             }
         }
         foreach ($fixtureFiles as $fixtureFilePath) {
             $fixture = YamlFixture::create($fixtureFilePath);
             $fixture->writeInto($this->getFixtureFactory());
         }
         $this->logInWithPermission("ADMIN");
     }
     // Preserve memory settings
     $this->originalMemoryLimit = ini_get('memory_limit');
     // turn off template debugging
     SSViewer::config()->update('source_file_comments', false);
     // Clear requirements
     Requirements::clear();
     // Set up email
     $this->mailer = new TestMailer();
     Injector::inst()->registerService($this->mailer, 'SilverStripe\\Control\\Email\\Mailer');
     Email::config()->remove('send_all_emails_to');
 }