/** * Test that the main-site user with ADMIN permissions can access all subsites, regardless * of whether he is in a subsite-specific group or not. */ function testMainsiteAdminCanAccessAllSubsites() { $member = $this->objFromFixture('Member', 'admin'); Session::set("loggedInAs", $member->ID); $cmsMain = new CMSMain(); foreach ($cmsMain->Subsites() as $subsite) { $ids[$subsite->ID] = true; } $this->assertArrayHasKey(0, $ids, "Main site accessible"); $this->assertArrayHasKey($this->idFromFixture('Subsite', 'main'), $ids, "Site with no groups inaccesible"); $this->assertArrayHasKey($this->idFromFixture('Subsite', 'subsite1'), $ids, "Subsite1 Template inaccessible"); $this->assertArrayHasKey($this->idFromFixture('Subsite', 'subsite2'), $ids, "Subsite2 Template inaccessible"); }
public function Breadcrumbs($unlinked = false) { $items = parent::Breadcrumbs($unlinked); //special case for building the breadcrumbs when calling the listchildren Pages ListView action if ($parentID = $this->getRequest()->getVar('ParentID')) { $page = DataObject::get_by_id('SiteTree', $parentID); //build a reversed list of the parent tree $pages = array(); while ($page) { array_unshift($pages, $page); //add to start of array so that array is in reverse order $page = $page->Parent; } //turns the title and link of the breadcrumbs into template-friendly variables $params = array_filter(array('view' => $this->getRequest()->getVar('view'), 'q' => $this->getRequest()->getVar('q'))); foreach ($pages as $page) { $params['ParentID'] = $page->ID; $item = new StdClass(); $item->Title = $page->Title; $item->Link = Controller::join_links($this->Link(), '?' . http_build_query($params)); $items->push(new ArrayData($item)); } } return $items; }
function getEditForm($id = null, $fields = null) { $record = $this->getRecord($id ? $id : $this->currentPageID()); $form = parent::getEditForm($record, $record ? $record->getCMSFields() : null); // TODO Replace with preview button $form->Fields()->addFieldToTab('Root.Main', new LiteralField('SwitchView', sprintf('<div class="cms-switch-view field"><label>Preview:</label><div class="middleColumn">%s</div></div>', $this->SwitchView()))); return $form; }
/** * Returns the read only version of the edit form. Detaches all {@link FormAction} * instances attached since only action relates to revert. * * Permission checking is done at the {@link CMSMain::getEditForm()} level. * * @param int $id ID of the record to show * @param array $fields optional * @param int $versionID * @param int $compare Compare mode * * @return Form */ function getEditForm($id = null, $fields = null, $versionID = null, $compareID = null) { if (!$id) { $id = $this->currentPageID(); } $record = $this->getRecord($id, $versionID); $versionID = $record ? $record->Version : $versionID; $form = parent::getEditForm($record, $record ? $record->getCMSFields() : null); // Respect permission failures from parent implementation if (!$form instanceof Form) { return $form; } $nav = new SilverStripeNavigatorItem_ArchiveLink($record); $form->setActions(new FieldList($revert = FormAction::create('doRollback', _t('CMSPageHistoryController.REVERTTOTHISVERSION', 'Revert to this version'))->setUseButtonTag(true), $navField = new LiteralField('ArchivedLink', $nav->getHTML()))); $fields = $form->Fields(); $fields->removeByName("Status"); $fields->push(new HiddenField("ID")); $fields->push(new HiddenField("Version")); $fields = $fields->makeReadonly(); $navField->setAllowHTML(true); foreach ($fields->dataFields() as $field) { $field->dontEscape = true; $field->reserveNL = true; } if ($compareID) { $link = Controller::join_links($this->Link('show'), $id); $view = _t('CMSPageHistoryController.VIEW', "view"); $message = _t('CMSPageHistoryController.COMPARINGVERSION', "Comparing versions {version1} and {version2}.", array('version1' => sprintf('%s (<a href="%s">%s</a>)', $versionID, Controller::join_links($link, $versionID), $view), 'version2' => sprintf('%s (<a href="%s">%s</a>)', $compareID, Controller::join_links($link, $compareID), $view))); $revert->setReadonly(true); } else { $message = _t('CMSPageHistoryController.VIEWINGVERSION', "Currently viewing version {version}.", array('version' => $versionID)); } $fields->addFieldToTab('Root.Main', new LiteralField('CurrentlyViewingMessage', $this->customise(array('Content' => $message, 'Classes' => 'notice'))->renderWith(array('CMSMain_notice'))), "Title"); $form->setFields($fields->makeReadonly()); $form->loadDataFrom(array("ID" => $id, "Version" => $versionID)); if ($record && $record->isLatestVersion()) { $revert->setReadonly(true); } $form->removeExtraClass('cms-content'); return $form; }
/** * Tests filtering in {@see CMSMain::getList()} */ public function testGetList() { $controller = new CMSMain(); // Test all pages (stage) $pages = $controller->getList()->sort('Title'); $this->assertEquals(28, $pages->count()); $this->assertEquals(array('Home', 'Page 1', 'Page 10', 'Page 11', 'Page 12'), $pages->Limit(5)->column('Title')); // Change state of tree $page1 = $this->objFromFixture('Page', 'page1'); $page3 = $this->objFromFixture('Page', 'page3'); $page11 = $this->objFromFixture('Page', 'page11'); $page12 = $this->objFromFixture('Page', 'page12'); // Deleted $page1->doUnpublish(); $page1->delete(); // Live and draft $page11->publish('Stage', 'Live'); // Live only $page12->publish('Stage', 'Live'); $page12->delete(); // Re-test all pages (stage) $pages = $controller->getList()->sort('Title'); $this->assertEquals(26, $pages->count()); $this->assertEquals(array('Home', 'Page 10', 'Page 11', 'Page 13', 'Page 14'), $pages->Limit(5)->column('Title')); // Test deleted page filter $params = array('FilterClass' => 'CMSSiteTreeFilter_StatusDeletedPages'); $pages = $controller->getList($params); $this->assertEquals(1, $pages->count()); $this->assertEquals(array('Page 1'), $pages->column('Title')); // Test live, but not on draft filter $params = array('FilterClass' => 'CMSSiteTreeFilter_StatusRemovedFromDraftPages'); $pages = $controller->getList($params); $this->assertEquals(1, $pages->count()); $this->assertEquals(array('Page 12'), $pages->column('Title')); // Test live pages filter $params = array('FilterClass' => 'CMSSIteTreeFilter_PublishedPages'); $pages = $controller->getList($params); $this->assertEquals(2, $pages->count()); $this->assertEquals(array('Page 11', 'Page 12'), $pages->column('Title')); // Test that parentID is ignored when filtering $pages = $controller->getList($params, $page3->ID); $this->assertEquals(2, $pages->count()); $this->assertEquals(array('Page 11', 'Page 12'), $pages->column('Title')); // Test that parentID is respected when not filtering $pages = $controller->getList(array(), $page3->ID); $this->assertEquals(2, $pages->count()); $this->assertEquals(array('Page 3.1', 'Page 3.2'), $pages->column('Title')); }
function getEditForm($id = null, $fields = null) { $record = $this->getRecord($id ? $id : $this->currentPageID()); return parent::getEditForm($record, $record ? $record->getSettingsFields() : null); }
function testGetVersionRecord() { $cmsMain = new CMSMain(); $page1 = $this->objFromFixture('Page', 'page1'); $page1->Content = 'this is the old content'; $page1->write(); $page1->Content = 'this is new content (new version)'; $page1->write(); $versionID = DB::query('SELECT "Version" FROM "SiteTree_versions" WHERE "Content" = \'this is the old content\'')->value(); $_REQUEST['Version'] = $versionID; $this->assertEquals($cmsMain->getRecord($page1->ID)->Version, $versionID); $this->assertEquals($cmsMain->getRecord($page1->ID)->Content, 'this is the old content'); unset($_REQUEST['Version']); }
function __construct() { $this->ids = array(); $this->expanded = array(); $where = array(); // Match against URLSegment, Title, MenuTitle & Content if (isset($_REQUEST['SiteTreeSearchTerm'])) { $term = Convert::raw2sql($_REQUEST['SiteTreeSearchTerm']); $where[] = "\"URLSegment\" LIKE '%{$term}%' OR \"Title\" LIKE '%{$term}%' OR \"MenuTitle\" LIKE '%{$term}%' OR \"Content\" LIKE '%{$term}%'"; } // Match against date if (isset($_REQUEST['SiteTreeFilterDate'])) { $date = $_REQUEST['SiteTreeFilterDate']; $date = (int) substr($date, 6, 4) . '-' . (int) substr($date, 3, 2) . '-' . (int) substr($date, 0, 2); $where[] = "\"LastEdited\" > '{$date}'"; } // Match against exact ClassName if (isset($_REQUEST['ClassName']) && $_REQUEST['ClassName'] != 'All') { $klass = Convert::raw2sql($_REQUEST['ClassName']); $where[] = "\"ClassName\" = '{$klass}'"; } // Partial string match against a variety of fields foreach (CMSMain::T_SiteTreeFilterOptions() as $key => $value) { if (!empty($_REQUEST[$key])) { $match = Convert::raw2sql($_REQUEST[$key]); $where[] = "\"{$key}\" LIKE '%{$match}%'"; } } $where = empty($where) ? '' : 'WHERE (' . implode(') AND (', $where) . ')'; $parents = array(); /* Do the actual search */ $res = DB::query('SELECT "ParentID", "ID" FROM "SiteTree" ' . $where); if (!$res) { return; } /* And keep a record of parents we don't need to get parents of themselves, as well as IDs to mark */ foreach ($res as $row) { if ($row['ParentID']) { $parents[$row['ParentID']] = true; } $this->ids[$row['ID']] = true; } /* We need to recurse up the tree, finding ParentIDs for each ID until we run out of parents */ while (!empty($parents)) { $res = DB::query('SELECT "ParentID", "ID" FROM "SiteTree" WHERE "ID" in (' . implode(',', array_keys($parents)) . ')'); $parents = array(); foreach ($res as $row) { if ($row['ParentID']) { $parents[$row['ParentID']] = true; } $this->ids[$row['ID']] = true; $this->expanded[$row['ID']] = true; } } }
/** * Retun an array of maps containing the keys, 'ID' and 'ParentID' for each page to be displayed * in the search. */ function pagesIncluded() { $data = $this->data; $this->ids = array(); $this->expanded = array(); $where = array(); // Match against URLSegment, Title, MenuTitle & Content if (isset($data['SiteTreeSearchTerm'])) { $term = Convert::raw2sql($data['SiteTreeSearchTerm']); $where[] = "\"URLSegment\" LIKE '%{$term}%' OR \"Title\" LIKE '%{$term}%' OR \"MenuTitle\" LIKE '%{$term}%' OR \"Content\" LIKE '%{$term}%'"; } // Match against date if (isset($data['SiteTreeFilterDate'])) { $date = $data['SiteTreeFilterDate']; $date = (int) substr($date, 6, 4) . '-' . (int) substr($date, 3, 2) . '-' . (int) substr($date, 0, 2); $where[] = "\"LastEdited\" > '{$date}'"; } // Match against exact ClassName if (isset($data['ClassName']) && $data['ClassName'] != 'All') { $klass = Convert::raw2sql($data['ClassName']); $where[] = "\"ClassName\" = '{$klass}'"; } // Partial string match against a variety of fields foreach (CMSMain::T_SiteTreeFilterOptions() as $key => $value) { if (!empty($data[$key])) { $match = Convert::raw2sql($data[$key]); $where[] = "\"{$key}\" LIKE '%{$match}%'"; } } $where = empty($where) ? '' : 'WHERE (' . implode(') AND (', $where) . ')'; $parents = array(); /* Do the actual search */ $res = DB::query('SELECT "ParentID", "ID" FROM "SiteTree" ' . $where); return $res; }
public function Breadcrumbs($unlinked = false) { $crumbs = parent::Breadcrumbs($unlinked); $crumbs[0]->Title = _t('CMSPagesController.MENUTITLE'); return $crumbs; }
/** * Test CMSMain::getRecord() */ function testGetRecord() { // Set up a page that is delete from live $page1 = $this->objFromFixture('Page', 'page1'); $page1ID = $page1->ID; $page1->doPublish(); $page1->delete(); $cmsMain = new CMSMain(); // Bad calls $this->assertNull($cmsMain->getRecord('0')); $this->assertNull($cmsMain->getRecord('asdf')); // Pages that are on draft and aren't on draft should both work $this->assertType('Page', $cmsMain->getRecord($page1ID)); $this->assertType('Page', $cmsMain->getRecord($this->idFromFixture('Page', 'page2'))); // This functionality isn't actually used any more. $newPage = $cmsMain->getRecord('new-Page-5'); $this->assertType('Page', $newPage); $this->assertEquals('5', $newPage->ParentID); }
function testSavePageInCMS() { $adminUser = $this->objFromFixture('Member', 'admin'); $enPage = $this->objFromFixture('Page', 'testpage_en'); $group = new Group(); $group->Title = 'Example Group'; $group->write(); $frPage = $enPage->createTranslation('fr_FR'); $frPage->write(); $adminUser->logIn(); $cmsMain = new CMSMain(); $origLocale = Translatable::get_current_locale(); Translatable::set_current_locale('fr_FR'); $form = $cmsMain->getEditForm($frPage->ID); $form->loadDataFrom(array('Title' => 'Translated', 'ViewerGroups' => $group->ID)); $form->saveInto($frPage); $frPage->write(); $this->assertEquals('Translated', $frPage->Title); $this->assertEquals(array($group->ID), $frPage->ViewerGroups()->column('ID')); $adminUser->logOut(); Translatable::set_current_locale($origLocale); }
function testPageTypesBlacklistInCMSMain() { $editor = $this->objFromFixture('Member', 'editor'); Session::set("loggedInAs", $editor->ID); $cmsmain = new CMSMain(); $s1 = $this->objFromFixture('Subsite', 'domaintest1'); $s2 = $this->objFromFixture('Subsite', 'domaintest2'); $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; $s1->write(); Subsite::changeSubsite($s1); $hints = Convert::json2array($cmsmain->SiteTreeHints()); $classes = $hints['Root']['disallowedChildren']; $this->assertContains('ErrorPage', $classes); $this->assertContains('SiteTreeSubsitesTest_ClassA', $classes); $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); Subsite::changeSubsite($s2); $hints = Convert::json2array($cmsmain->SiteTreeHints()); $classes = $hints['Root']['disallowedChildren']; $this->assertNotContains('ErrorPage', $classes); $this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes); $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); }
/** * Get the fields * * @param Int $id * @param FieldList $fields * @return FieldList */ public function getEditForm($id = null, $fields = null) { $record = $this->getRecord($id ? $id : $this->currentPageID()); return parent::getEditForm($record, $record ? $record->getBlockBuilderFields() : null); }
function cmsMainMarkingFilterFunction($node) { // Expand all nodes // $node->markingFinished(); $failed_filter = false; // First check for the generic search term in the URLSegment, Title, MenuTitle, & Content if (!empty($_REQUEST['SiteTreeSearchTerm'])) { // For childless nodes, show only those matching the filter $filter = strtolower($_REQUEST['SiteTreeSearchTerm']); if (strpos(strtolower($node->URLSegment), $filter) === false && strpos(strtolower($node->Title), $filter) === false && strpos(strtolower($node->MenuTitle), $filter) === false && strpos(strtolower($node->Content), $filter) === false) { $failed_filter = true; } } // Check the 'Edited Since' date if (!empty($_REQUEST['SiteTreeFilterDate'])) { $edited_since = mktime(0, 0, 0, substr($_REQUEST['SiteTreeFilterDate'], 3, 2), substr($_REQUEST['SiteTreeFilterDate'], 0, 2), substr($_REQUEST['SiteTreeFilterDate'], 6, 4)); if (strtotime($node->LastEdited) < $edited_since) { $failed_filter = true; } } // Now check if a specified Criteria attribute matches foreach (CMSMain::T_SiteTreeFilterOptions() as $key => $value) { if (!empty($_REQUEST[$key])) { $parameterName = $key; $filter = strtolower($_REQUEST[$key]); // Show node only if the filter string exists anywere in the filter paramater (ignoring case) if (strpos(strtolower($node->{$parameterName}), $filter) === false) { $failed_filter = true; } } } // Each filter must match or it fails if (true == $failed_filter) { // Don't ever hide nodes with children, because otherwise if one of their children matches the search, it wouldn't be shown. if ($node->AllChildrenIncludingDeleted()->count() > 0) { // Open all nodes with children so it is easy to see any children that match the search. foreach ($node->AllChildrenIncludingDeleted() as $childNode) { if (cmsMainMarkingFilterFunction($childNode)) { $node->markOpened(); $filterCache[$node->ID] = true; return true; } } } $filterCache[$node->ID] = false; return false; } else { if ($node->AllChildrenIncludingDeleted()->count() > 0) { $node->markOpened(); } $filterCache[$node->ID] = true; return true; } }
/** * Testing retrieval and type of CMS edit form. */ public function testGetEditForm() { // Login is required prior to accessing a CMS form. $this->loginWithPermission('ADMIN'); // Get a associated with a fixture page. $page = $this->objFromFixture('Page', 'page1'); $controller = new CMSMain(); $form = $controller->getEditForm($page->ID); $this->assertInstanceOf("CMSForm", $form); // Ensure that the form will not "validate" on delete or "unpublish" actions. $exemptActions = $form->getValidationExemptActions(); $this->assertContains("delete", $exemptActions); $this->assertContains("unpublish", $exemptActions); }
/** * Get the actions available in the CMS for this page - eg Save, Publish. * * Frontend scripts and styles know how to handle the following FormFields: * - top-level FormActions appear as standalone buttons * - top-level CompositeField with FormActions within appear as grouped buttons * - TabSet & Tabs appear as a drop ups * - FormActions within the Tab are restyled as links * - major actions can provide alternate states for richer presentation (see ssui.button widget extension) * * @return FieldList The available actions for this page. */ public function getCMSActions() { $existsOnLive = $this->getExistsOnLive(); // Major actions appear as buttons immediately visible as page actions. $majorActions = CompositeField::create()->setName('MajorActions')->setTag('fieldset')->addExtraClass('ss-ui-buttonset'); // Minor options are hidden behind a drop-up and appear as links (although they are still FormActions). $rootTabSet = new TabSet('ActionMenus'); $moreOptions = new Tab('MoreOptions', _t('SiteTree.MoreOptions', 'More options', 'Expands a view for more buttons')); $rootTabSet->push($moreOptions); $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus'); // Render page information into the "more-options" drop-up, on the top. $live = Versioned::get_one_by_stage('SiteTree', 'Live', array('"SiteTree"."ID"' => $this->ID)); $moreOptions->push(new LiteralField('Information', $this->customise(array('Live' => $live, 'ExistsOnLive' => $existsOnLive))->renderWith('SiteTree_Information'))); // "readonly"/viewing version that isn't the current version of the record $stageOrLiveRecord = Versioned::get_one_by_stage($this->class, Versioned::current_stage(), array('"SiteTree"."ID"' => $this->ID)); if ($stageOrLiveRecord && $stageOrLiveRecord->Version != $this->Version) { $moreOptions->push(FormAction::create('email', _t('CMSMain.EMAIL', 'Email'))); $moreOptions->push(FormAction::create('rollback', _t('CMSMain.ROLLBACK', 'Roll back to this version'))); $actions = new FieldList(array($majorActions, $rootTabSet)); // getCMSActions() can be extended with updateCMSActions() on a extension $this->extend('updateCMSActions', $actions); return $actions; } if ($this->isPublished() && $this->canPublish() && !$this->getIsDeletedFromStage() && $this->canDeleteFromLive()) { // "unpublish" $moreOptions->push(FormAction::create('unpublish', _t('SiteTree.BUTTONUNPUBLISH', 'Unpublish'), 'delete')->setDescription(_t('SiteTree.BUTTONUNPUBLISHDESC', 'Remove this page from the published site'))->addExtraClass('ss-ui-action-destructive')); } if ($this->stagesDiffer('Stage', 'Live') && !$this->getIsDeletedFromStage()) { if ($this->isPublished() && $this->canEdit()) { // "rollback" $moreOptions->push(FormAction::create('rollback', _t('SiteTree.BUTTONCANCELDRAFT', 'Cancel draft changes'), 'delete')->setDescription(_t('SiteTree.BUTTONCANCELDRAFTDESC', 'Delete your draft and revert to the currently published page'))); } } if ($this->canEdit()) { if ($this->getIsDeletedFromStage()) { // The usual major actions are not available, so we provide alternatives here. if ($existsOnLive) { // "restore" $majorActions->push(FormAction::create('revert', _t('CMSMain.RESTORE', 'Restore'))); if ($this->canDelete() && $this->canDeleteFromLive()) { // "delete from live" $majorActions->push(FormAction::create('deletefromlive', _t('CMSMain.DELETEFP', 'Delete'))->addExtraClass('ss-ui-action-destructive')); } } else { // Determine if we should force a restore to root (where once it was a subpage) $restoreToRoot = $this->isParentArchived(); // "restore" $title = $restoreToRoot ? _t('CMSMain.RESTORE_TO_ROOT', 'Restore draft at top level') : _t('CMSMain.RESTORE', 'Restore draft'); $description = $restoreToRoot ? _t('CMSMain.RESTORE_TO_ROOT_DESC', 'Restore the archived version to draft as a top level page') : _t('CMSMain.RESTORE_DESC', 'Restore the archived version to draft'); $majorActions->push(FormAction::create('restore', $title)->setDescription($description)->setAttribute('data-to-root', $restoreToRoot)->setAttribute('data-icon', 'decline')); } } else { // Detect use of legacy actions // {@see CMSMain::enabled_legacy_actions} $legacy = CMSMain::config()->enabled_legacy_actions; if (in_array('CMSBatchAction_Delete', $legacy)) { Deprecation::notice('4.0', 'Delete from Stage is deprecated. Use Archive instead.'); if ($this->canDelete()) { // delete $moreOptions->push(FormAction::create('delete', _t('CMSMain.DELETE', 'Delete draft'))->addExtraClass('delete ss-ui-action-destructive')); } } elseif ($this->canArchive()) { // "archive" $moreOptions->push(FormAction::create('archive', _t('CMSMain.ARCHIVE', 'Archive'))->setDescription(_t('SiteTree.BUTTONARCHIVEDESC', 'Unpublish and send to archive'))->addExtraClass('delete ss-ui-action-destructive')); } // "save", supports an alternate state that is still clickable, but notifies the user that the action is not needed. $majorActions->push(FormAction::create('save', _t('SiteTree.BUTTONSAVED', 'Saved'))->setAttribute('data-icon', 'accept')->setAttribute('data-icon-alternate', 'addpage')->setAttribute('data-text-alternate', _t('CMSMain.SAVEDRAFT', 'Save draft'))); } } if ($this->canPublish() && !$this->getIsDeletedFromStage()) { // "publish", as with "save", it supports an alternate state to show when action is needed. $majorActions->push($publish = FormAction::create('publish', _t('SiteTree.BUTTONPUBLISHED', 'Published'))->setAttribute('data-icon', 'accept')->setAttribute('data-icon-alternate', 'disk')->setAttribute('data-text-alternate', _t('SiteTree.BUTTONSAVEPUBLISH', 'Save & publish'))); // Set up the initial state of the button to reflect the state of the underlying SiteTree object. if ($this->stagesDiffer('Stage', 'Live')) { $publish->addExtraClass('ss-ui-alternate'); } } $actions = new FieldList(array($majorActions, $rootTabSet)); // Hook for extensions to add/remove actions. $this->extend('updateCMSActions', $actions); return $actions; }
function init() { parent::init(); Requirements::javascript(CMS_DIR . '/javascript/CMSPagesController.Tree.js'); }
<?php /** * The subsites module modifies the behaviour of the CMS - in the SiteTree and Group databases - to store information * about a number of sub-sites, rather than a single site. */ SiteTree::add_extension('SiteTreeSubsites'); ContentController::add_extension('ControllerSubsites'); CMSPageAddController::add_extension('CMSPageAddControllerExtension'); LeftAndMain::add_extension('LeftAndMainSubsites'); LeftAndMain::add_extension('ControllerSubsites'); Group::add_extension('GroupSubsites'); ErrorPage::add_extension('ErrorPageSubsite'); SiteConfig::add_extension('SiteConfigSubsites'); SS_Report::add_excluded_reports('SubsiteReportWrapper'); //Display in cms menu SecurityAdmin::add_extension('SubsiteMenuExtension'); CMSMain::add_extension('SubsiteMenuExtension'); CMSPagesController::add_extension('SubsiteMenuExtension'); SubsiteAdmin::add_extension('SubsiteMenuExtension'); CMSSettingsController::add_extension('SubsiteMenuExtension'); CMSMenu::remove_menu_item('SubsiteXHRController');