From c5f507b3f9cb647770f6800d40f58cd73724d2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Werner=20M=2E=20Krau=C3=9F?= Date: Wed, 24 May 2017 15:26:28 +0200 Subject: [PATCH] reformat code and tests --- code/admin/SubsiteAdmin.php | 34 +- code/controller/SubsiteXHRController.php | 87 +- .../CMSPageAddControllerExtension.php | 12 +- code/extensions/ControllerSubsites.php | 30 +- code/extensions/ErrorPageSubsite.php | 72 +- code/extensions/FileSubsites.php | 264 +-- code/extensions/GroupSubsites.php | 373 ++-- code/extensions/LeftAndMainSubsites.php | 644 ++++--- code/extensions/SiteConfigSubsites.php | 109 +- code/extensions/SiteTreeSubsites.php | 754 ++++---- code/extensions/SubsiteMenuExtension.php | 15 +- code/forms/GridFieldSubsiteDetailForm.php | 104 +- code/forms/SubsitesTreeDropdownField.php | 56 +- code/model/Subsite.php | 1707 +++++++++-------- code/model/SubsiteDomain.php | 166 +- code/pages/SubsitesVirtualPage.php | 415 ++-- code/reports/SubsiteReportWrapper.php | 123 +- code/tasks/SubsiteCopyPagesTask.php | 134 +- tests/BaseSubsiteTest.php | 54 +- tests/FileSubsitesTest.php | 152 +- tests/GroupSubsitesTest.php | 49 +- tests/LeftAndMainSubsitesTest.php | 177 +- tests/SiteConfigSubsitesTest.php | 72 +- tests/SiteTreeSubsitesTest.php | 440 +++-- tests/SubsiteAdminFunctionalTest.php | 294 +-- tests/SubsiteAdminTest.php | 76 +- tests/SubsiteTest.php | 742 +++---- tests/SubsitesVirtualPageTest.php | 546 +++--- .../bootstrap/Context/FeatureContext.php | 58 +- .../behat/features/preview-navigation.feature | 46 +- 30 files changed, 4103 insertions(+), 3702 deletions(-) diff --git a/code/admin/SubsiteAdmin.php b/code/admin/SubsiteAdmin.php index 91a7f8b4..3a6a8dfb 100644 --- a/code/admin/SubsiteAdmin.php +++ b/code/admin/SubsiteAdmin.php @@ -13,30 +13,32 @@ * * @package subsites */ -class SubsiteAdmin extends ModelAdmin { +class SubsiteAdmin extends ModelAdmin +{ - private static $managed_models = array(Subsite::class); + private static $managed_models = [Subsite::class]; - private static $url_segment = 'subsites'; + private static $url_segment = 'subsites'; - private static $menu_title = "Subsites"; + private static $menu_title = "Subsites"; - private static $menu_icon = "subsites/images/subsites.png"; + private static $menu_icon = "subsites/images/subsites.png"; - public $showImportForm=false; + public $showImportForm = false; - private static $tree_class = Subsite::class; + private static $tree_class = Subsite::class; - public function getEditForm($id = null, $fields = null) { - $form = parent::getEditForm($id, $fields); + public function getEditForm($id = null, $fields = null) + { + $form = parent::getEditForm($id, $fields); - $grid=$form->Fields()->dataFieldByName(Subsite::class); - if($grid) { - $grid->getConfig()->removeComponentsByType('SilverStripe\\Forms\\GridField\\GridFieldDetailForm'); - $grid->getConfig()->addComponent(new GridFieldSubsiteDetailForm()); - } + $grid = $form->Fields()->dataFieldByName(Subsite::class); + if ($grid) { + $grid->getConfig()->removeComponentsByType('SilverStripe\\Forms\\GridField\\GridFieldDetailForm'); + $grid->getConfig()->addComponent(new GridFieldSubsiteDetailForm()); + } - return $form; - } + return $form; + } } diff --git a/code/controller/SubsiteXHRController.php b/code/controller/SubsiteXHRController.php index a0e01662..3670861b 100644 --- a/code/controller/SubsiteXHRController.php +++ b/code/controller/SubsiteXHRController.php @@ -10,43 +10,54 @@ /** * Section-agnostic PJAX controller. */ -class SubsiteXHRController extends LeftAndMain { - - /** - * Relax the access permissions, so anyone who has access to any CMS subsite can access this controller. - */ - public function canView($member = null) { - if (parent::canView()) return true; - - if (Subsite::all_accessible_sites()->count()>0) return true; - - return false; - } - - /** - * Similar as above, but for the LeftAndMainSubsites - allow access if user allowed into the CMS at all. - */ - public function canAccess() { - if (Subsite::all_accessible_sites()->count()>0) return true; - } - - public function getResponseNegotiator() { - $negotiator = parent::getResponseNegotiator(); - $self = $this; - - // Register a new callback - $negotiator->setCallback('SubsiteList', function() use(&$self) { - return $self->SubsiteList(); - }); - - return $negotiator; - } - - /** - * Provide the list of available subsites as a cms-section-agnostic PJAX handler. - */ - public function SubsiteList() { - return $this->renderWith('Includes/SubsiteList'); - } +class SubsiteXHRController extends LeftAndMain +{ + + /** + * Relax the access permissions, so anyone who has access to any CMS subsite can access this controller. + */ + public function canView($member = null) + { + if (parent::canView()) { + return true; + } + + if (Subsite::all_accessible_sites()->count() > 0) { + return true; + } + + return false; + } + + /** + * Similar as above, but for the LeftAndMainSubsites - allow access if user allowed into the CMS at all. + */ + public function canAccess() + { + if (Subsite::all_accessible_sites()->count() > 0) { + return true; + } + } + + public function getResponseNegotiator() + { + $negotiator = parent::getResponseNegotiator(); + $self = $this; + + // Register a new callback + $negotiator->setCallback('SubsiteList', function () use (&$self) { + return $self->SubsiteList(); + }); + + return $negotiator; + } + + /** + * Provide the list of available subsites as a cms-section-agnostic PJAX handler. + */ + public function SubsiteList() + { + return $this->renderWith('Includes/SubsiteList'); + } } diff --git a/code/extensions/CMSPageAddControllerExtension.php b/code/extensions/CMSPageAddControllerExtension.php index 9196f339..934677f0 100644 --- a/code/extensions/CMSPageAddControllerExtension.php +++ b/code/extensions/CMSPageAddControllerExtension.php @@ -3,14 +3,16 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\Forms\HiddenField; use SilverStripe\Core\Extension; +use SilverStripe\Forms\HiddenField; use SilverStripe\Subsites\Model\Subsite; -class CMSPageAddControllerExtension extends Extension { +class CMSPageAddControllerExtension extends Extension +{ - function updatePageOptions(&$fields) { - $fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID())); - } + function updatePageOptions(&$fields) + { + $fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID())); + } } diff --git a/code/extensions/ControllerSubsites.php b/code/extensions/ControllerSubsites.php index 6cf54bc6..14be6b91 100644 --- a/code/extensions/ControllerSubsites.php +++ b/code/extensions/ControllerSubsites.php @@ -3,26 +3,30 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\View\SSViewer; use SilverStripe\Core\Extension; use SilverStripe\Subsites\Model\Subsite; +use SilverStripe\View\SSViewer; /** * @package subsites */ -class ControllerSubsites extends Extension { - function controllerAugmentInit(){ - if($subsite = Subsite::currentSubsite()){ - if($theme = $subsite->Theme) - SSViewer::set_theme($theme); - } - } +class ControllerSubsites extends Extension +{ + function controllerAugmentInit() + { + if ($subsite = Subsite::currentSubsite()) { + if ($theme = $subsite->Theme) { + SSViewer::set_theme($theme); + } + } + } - function CurrentSubsite(){ - if($subsite = Subsite::currentSubsite()){ - return $subsite; - } - } + function CurrentSubsite() + { + if ($subsite = Subsite::currentSubsite()) { + return $subsite; + } + } } ?> diff --git a/code/extensions/ErrorPageSubsite.php b/code/extensions/ErrorPageSubsite.php index 05e67a9b..ec59106c 100644 --- a/code/extensions/ErrorPageSubsite.php +++ b/code/extensions/ErrorPageSubsite.php @@ -3,44 +3,46 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataExtension; +use SilverStripe\ORM\DataObject; use SilverStripe\Subsites\Model\Subsite; -class ErrorPageSubsite extends DataExtension { - - /** - * Alter file path to generated a static (static) error page file to handle error page template on different sub-sites - * - * {@see Error::get_error_filename()} - * - * FIXME since {@link Subsite::currentSubsite()} partly relies on Session, viewing other sub-site (including main site) between - * opening ErrorPage in the CMS and publish ErrorPage causes static error page to get generated incorrectly. - * - * @param string $name Filename to write to - * @param int $statusCode Integer error code - */ - public function updateErrorFilename(&$name, $statusCode) { - - // Try to get current subsite from session - $subsite = Subsite::currentSubsite(false); - - // since this function is called from Page class before the controller is created, we have to get subsite from domain instead - if(!$subsite) { - $subsiteID = Subsite::getSubsiteIDForDomain(); - if($subsiteID != 0) { - $subsite = DataObject::get_by_id(Subsite::class, $subsiteID); - } - } - - // Without subsite, don't rewrite - if($subsite) { - // Add subdomain to end of filename, just before .html - // This should preserve translatable locale in the filename as well - $subdomain = $subsite->domain(); - $name = substr($name, 0, -5) . "-{$subdomain}.html"; - } - } +class ErrorPageSubsite extends DataExtension +{ + + /** + * Alter file path to generated a static (static) error page file to handle error page template on different sub-sites + * + * {@see Error::get_error_filename()} + * + * FIXME since {@link Subsite::currentSubsite()} partly relies on Session, viewing other sub-site (including main site) between + * opening ErrorPage in the CMS and publish ErrorPage causes static error page to get generated incorrectly. + * + * @param string $name Filename to write to + * @param int $statusCode Integer error code + */ + public function updateErrorFilename(&$name, $statusCode) + { + + // Try to get current subsite from session + $subsite = Subsite::currentSubsite(false); + + // since this function is called from Page class before the controller is created, we have to get subsite from domain instead + if (!$subsite) { + $subsiteID = Subsite::getSubsiteIDForDomain(); + if ($subsiteID != 0) { + $subsite = DataObject::get_by_id(Subsite::class, $subsiteID); + } + } + + // Without subsite, don't rewrite + if ($subsite) { + // Add subdomain to end of filename, just before .html + // This should preserve translatable locale in the filename as well + $subdomain = $subsite->domain(); + $name = substr($name, 0, -5) . "-{$subdomain}.html"; + } + } } diff --git a/code/extensions/FileSubsites.php b/code/extensions/FileSubsites.php index bcfcbd90..d4810ae7 100644 --- a/code/extensions/FileSubsites.php +++ b/code/extensions/FileSubsites.php @@ -3,15 +3,15 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\Forms\FieldList; use SilverStripe\Assets\Folder; +use SilverStripe\Control\Session; use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\FieldList; use SilverStripe\Forms\LiteralField; -use SilverStripe\ORM\Queries\SQLSelect; +use SilverStripe\ORM\DataExtension; use SilverStripe\ORM\DataQuery; -use SilverStripe\Control\Session; +use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\Security\Permission; -use SilverStripe\ORM\DataExtension; use SilverStripe\Subsites\Model\Subsite; @@ -20,126 +20,142 @@ * * @package subsites */ -class FileSubsites extends DataExtension { - - // If this is set to true, all folders created will be default be - // considered 'global', unless set otherwise - static $default_root_folders_global = false; - - private static $has_one=array( - 'Subsite' => Subsite::class, - ); - - /** - * Amends the CMS tree title for folders in the Files & Images section. - * Prefixes a '* ' to the folders that are accessible from all subsites. - */ - function alternateTreeTitle() { - if($this->owner->SubsiteID == 0) return " * " . $this->owner->Title; - else return $this->owner->Title; - } - - /** - * Add subsites-specific fields to the folder editor. - */ - function updateCMSFields(FieldList $fields) { - if($this->owner instanceof Folder) { - $sites = Subsite::accessible_sites('CMS_ACCESS_AssetAdmin'); - $values = array(); - $values[0] = _t('FileSubsites.AllSitesDropdownOpt','All sites'); - foreach ($sites as $site) { - $values[$site->ID] = $site->Title; - } - ksort($values); - if($sites){ - //Dropdown needed to move folders between subsites - $dropdown = new DropdownField( - 'SubsiteID', - _t('FileSubsites.SubsiteFieldLabel',Subsite::class), - $values - ); - $dropdown->addExtraClass('subsites-move-dropdown'); - $fields->push($dropdown); - $fields->push(new LiteralField( - 'Message', - '

'. - _t('ASSETADMIN.SUBSITENOTICE', 'Folders and files created in the main site are accessible by all subsites.') - .'

' - )); - } - } - } - - /** - * Update any requests to limit the results to the current site - */ - public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { - if(Subsite::$disable_subsite_filter) return; - - // If you're querying by ID, ignore the sub-site - this is a bit ugly... (but it was WAYYYYYYYYY worse) - //@TODO I don't think excluding if SiteTree_ImageTracking is a good idea however because of the SS 3.0 api and ManyManyList::removeAll() changing the from table after this function is called there isn't much of a choice - - $from = $query->getFrom(); - if(isset($from['SiteTree_ImageTracking']) || $query->filtersOnID()) return; - - $subsiteID = (int) Subsite::currentSubsiteID(); - - // The foreach is an ugly way of getting the first key :-) - foreach($query->getFrom() as $tableName => $info) { - $where = "\"$tableName\".\"SubsiteID\" IN (0, $subsiteID)"; - $query->addWhere($where); - break; - } - - $sect=array_values($query->getSelect()); - $isCounting = strpos($sect[0], 'COUNT') !== false; - - // Ordering when deleting or counting doesn't apply - if(!$isCounting) { - $query->addOrderBy("\"SubsiteID\""); - } - } - - function onBeforeWrite() { - if (!$this->owner->ID && !$this->owner->SubsiteID) { - if (self::$default_root_folders_global) { - $this->owner->SubsiteID = 0; - } else { - $this->owner->SubsiteID = Subsite::currentSubsiteID(); - } - } - } - - function onAfterUpload() { - // If we have a parent, use it's subsite as our subsite - if ($this->owner->Parent()) { - $this->owner->SubsiteID = $this->owner->Parent()->SubsiteID; - } else { - $this->owner->SubsiteID = Subsite::currentSubsiteID(); - } - $this->owner->write(); - } - - function canEdit($member = null) { - // Check the CMS_ACCESS_SecurityAdmin privileges on the subsite that owns this group - $subsiteID = Session::get('SubsiteID'); - if($subsiteID&&$subsiteID == $this->owner->SubsiteID) { - return true; - } else { - Session::set('SubsiteID', $this->owner->SubsiteID); - $access = Permission::check(array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain')); - Session::set('SubsiteID', $subsiteID); - - return $access; - } - } - - /** - * Return a piece of text to keep DataObject cache keys appropriately specific - */ - function cacheKeyComponent() { - return 'subsite-'.Subsite::currentSubsiteID(); - } +class FileSubsites extends DataExtension +{ + + // If this is set to true, all folders created will be default be + // considered 'global', unless set otherwise + static $default_root_folders_global = false; + + private static $has_one = [ + 'Subsite' => Subsite::class, + ]; + + /** + * Amends the CMS tree title for folders in the Files & Images section. + * Prefixes a '* ' to the folders that are accessible from all subsites. + */ + function alternateTreeTitle() + { + if ($this->owner->SubsiteID == 0) { + return " * " . $this->owner->Title; + } else { + return $this->owner->Title; + } + } + + /** + * Add subsites-specific fields to the folder editor. + */ + function updateCMSFields(FieldList $fields) + { + if ($this->owner instanceof Folder) { + $sites = Subsite::accessible_sites('CMS_ACCESS_AssetAdmin'); + $values = []; + $values[0] = _t('FileSubsites.AllSitesDropdownOpt', 'All sites'); + foreach ($sites as $site) { + $values[$site->ID] = $site->Title; + } + ksort($values); + if ($sites) { + //Dropdown needed to move folders between subsites + $dropdown = new DropdownField( + 'SubsiteID', + _t('FileSubsites.SubsiteFieldLabel', Subsite::class), + $values + ); + $dropdown->addExtraClass('subsites-move-dropdown'); + $fields->push($dropdown); + $fields->push(new LiteralField( + 'Message', + '

' . + _t('ASSETADMIN.SUBSITENOTICE', + 'Folders and files created in the main site are accessible by all subsites.') + . '

' + )); + } + } + } + + /** + * Update any requests to limit the results to the current site + */ + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) + { + if (Subsite::$disable_subsite_filter) { + return; + } + + // If you're querying by ID, ignore the sub-site - this is a bit ugly... (but it was WAYYYYYYYYY worse) + //@TODO I don't think excluding if SiteTree_ImageTracking is a good idea however because of the SS 3.0 api and ManyManyList::removeAll() changing the from table after this function is called there isn't much of a choice + + $from = $query->getFrom(); + if (isset($from['SiteTree_ImageTracking']) || $query->filtersOnID()) { + return; + } + + $subsiteID = (int)Subsite::currentSubsiteID(); + + // The foreach is an ugly way of getting the first key :-) + foreach ($query->getFrom() as $tableName => $info) { + $where = "\"$tableName\".\"SubsiteID\" IN (0, $subsiteID)"; + $query->addWhere($where); + break; + } + + $sect = array_values($query->getSelect()); + $isCounting = strpos($sect[0], 'COUNT') !== false; + + // Ordering when deleting or counting doesn't apply + if (!$isCounting) { + $query->addOrderBy("\"SubsiteID\""); + } + } + + function onBeforeWrite() + { + if (!$this->owner->ID && !$this->owner->SubsiteID) { + if (self::$default_root_folders_global) { + $this->owner->SubsiteID = 0; + } else { + $this->owner->SubsiteID = Subsite::currentSubsiteID(); + } + } + } + + function onAfterUpload() + { + // If we have a parent, use it's subsite as our subsite + if ($this->owner->Parent()) { + $this->owner->SubsiteID = $this->owner->Parent()->SubsiteID; + } else { + $this->owner->SubsiteID = Subsite::currentSubsiteID(); + } + $this->owner->write(); + } + + function canEdit($member = null) + { + // Check the CMS_ACCESS_SecurityAdmin privileges on the subsite that owns this group + $subsiteID = Session::get('SubsiteID'); + if ($subsiteID && $subsiteID == $this->owner->SubsiteID) { + return true; + } else { + Session::set('SubsiteID', $this->owner->SubsiteID); + $access = Permission::check(['CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain']); + Session::set('SubsiteID', $subsiteID); + + return $access; + } + } + + /** + * Return a piece of text to keep DataObject cache keys appropriately specific + */ + function cacheKeyComponent() + { + return 'subsite-' . Subsite::currentSubsiteID(); + } } diff --git a/code/extensions/GroupSubsites.php b/code/extensions/GroupSubsites.php index 1fe8a339..0ee18d21 100644 --- a/code/extensions/GroupSubsites.php +++ b/code/extensions/GroupSubsites.php @@ -3,19 +3,19 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\ORM\DB; -use SilverStripe\Forms\FieldList; +use SilverStripe\Control\Cookie; use SilverStripe\Core\Convert; -use SilverStripe\Forms\OptionsetField; use SilverStripe\Forms\CheckboxSetField; +use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\OptionsetField; use SilverStripe\Forms\ReadonlyField; -use SilverStripe\ORM\Queries\SQLSelect; -use SilverStripe\ORM\DataQuery; -use SilverStripe\Control\Cookie; use SilverStripe\ORM\DataExtension; +use SilverStripe\ORM\DataQuery; +use SilverStripe\ORM\DB; +use SilverStripe\ORM\Queries\SQLSelect; +use SilverStripe\Security\Group; use SilverStripe\Security\PermissionProvider; use SilverStripe\Subsites\Model\Subsite; -use SilverStripe\Security\Group; /** @@ -23,185 +23,206 @@ * * @package subsites */ -class GroupSubsites extends DataExtension implements PermissionProvider { - - private static $db = array( - 'AccessAllSubsites' => 'Boolean' - ); - - private static $many_many = array( - 'Subsites' => Subsite::class - ); - - private static $defaults = array( - 'AccessAllSubsites' => true - ); - - /** - * Migrations for GroupSubsites data. - */ - function requireDefaultRecords() { - // Migration for Group.SubsiteID data from when Groups only had a single subsite +class GroupSubsites extends DataExtension implements PermissionProvider +{ + + private static $db = [ + 'AccessAllSubsites' => 'Boolean' + ]; + + private static $many_many = [ + 'Subsites' => Subsite::class + ]; + + private static $defaults = [ + 'AccessAllSubsites' => true + ]; + + /** + * Migrations for GroupSubsites data. + */ + function requireDefaultRecords() + { + // Migration for Group.SubsiteID data from when Groups only had a single subsite $schema = $this->owner->getSchema(); - $groupFields = DB::field_list($schema->tableName(Group::class)); + $groupFields = DB::field_list($schema->tableName(Group::class)); - // Detection of SubsiteID field is the trigger for old-style-subsiteID migration - if(isset($groupFields['SubsiteID'])) { - // Migrate subsite-specific data - DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID") + // Detection of SubsiteID field is the trigger for old-style-subsiteID migration + if (isset($groupFields['SubsiteID'])) { + // Migrate subsite-specific data + DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID") SELECT "ID", "SubsiteID" FROM "Group" WHERE "SubsiteID" > 0'); - // Migrate global-access data - DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0'); + // Migrate global-access data + DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0'); - // Move the field out of the way so that this migration doesn't get executed again - DB::get_schema()->renameField(Group::class, 'SubsiteID', '_obsolete_SubsiteID'); + // Move the field out of the way so that this migration doesn't get executed again + DB::get_schema()->renameField(Group::class, 'SubsiteID', '_obsolete_SubsiteID'); - // No subsite access on anything means that we've just installed the subsites module. - // Make all previous groups global-access groups - } else if(!DB::query('SELECT "Group"."ID" FROM "Group" + // No subsite access on anything means that we've just installed the subsites module. + // Make all previous groups global-access groups + } else { + if (!DB::query('SELECT "Group"."ID" FROM "Group" LEFT JOIN "Group_Subsites" ON "Group_Subsites"."GroupID" = "Group"."ID" AND "Group_Subsites"."SubsiteID" > 0 WHERE "AccessAllSubsites" = 1 - OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()) { - - DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1'); - } - } - - function updateCMSFields(FieldList $fields) { - if($this->owner->canEdit() ){ - // i18n tab - $fields->findOrMakeTab('Root.Subsites',_t('GroupSubsites.SECURITYTABTITLE','Subsites')); - - $subsites = Subsite::accessible_sites(array('ADMIN', 'SECURITY_SUBSITE_GROUP'), true); - $subsiteMap = $subsites->map(); - - // Prevent XSS injection - $subsiteMap = Convert::raw2xml($subsiteMap); - - // Interface is different if you have the rights to modify subsite group values on - // all subsites - if(isset($subsiteMap[0])) { - $fields->addFieldToTab("Root.Subsites", new OptionsetField("AccessAllSubsites", - _t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'), - array( - 1 => _t('GroupSubsites.ACCESSALL', "All subsites"), - 0 => _t('GroupSubsites.ACCESSONLY', "Only these subsites"), - ) - )); - - unset($subsiteMap[0]); - $fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites", "", - $subsiteMap)); - - } else { - if (sizeof($subsiteMap) <= 1) { - $fields->addFieldToTab("Root.Subsites", new ReadonlyField("SubsitesHuman", - _t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'), - reset($subsiteMap))); - } else { - $fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites", - _t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'), - $subsiteMap)); - } - } - } - } - - /** - * If this group belongs to a subsite, - * append the subsites title to the group title - * to make it easy to distinguish in the tree-view - * of the security admin interface. - */ - function alternateTreeTitle() { - if($this->owner->AccessAllSubsites) { - $title = _t('GroupSubsites.GlobalGroup', 'global group'); - return htmlspecialchars($this->owner->Title, ENT_QUOTES) . ' (' . $title . ')'; - } else { - $subsites = Convert::raw2xml(implode(", ", $this->owner->Subsites()->column('Title'))); - return htmlspecialchars($this->owner->Title) . " ($subsites)"; - } - } - - /** - * Update any requests to limit the results to the current site - */ - public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { - if(Subsite::$disable_subsite_filter) return; - if(Cookie::get('noSubsiteFilter') == 'true') return; - - // If you're querying by ID, ignore the sub-site - this is a bit ugly... - if(!$query->filtersOnID()) { - - /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; - else */$subsiteID = (int)Subsite::currentSubsiteID(); - - // Don't filter by Group_Subsites if we've already done that - $hasGroupSubsites = false; - foreach($query->getFrom() as $item) { - if((is_array($item) && strpos($item['table'], 'Group_Subsites')!==false) || (!is_array($item) && strpos($item, 'Group_Subsites')!==false)) { - $hasGroupSubsites = true; - break; - } - } - - if(!$hasGroupSubsites) { - if($subsiteID) { - $query->addLeftJoin("Group_Subsites", "\"Group_Subsites\".\"GroupID\" + OR "Group_Subsites"."GroupID" IS NOT NULL ')->value() + ) { + + DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1'); + } + } + } + + function updateCMSFields(FieldList $fields) + { + if ($this->owner->canEdit()) { + // i18n tab + $fields->findOrMakeTab('Root.Subsites', _t('GroupSubsites.SECURITYTABTITLE', 'Subsites')); + + $subsites = Subsite::accessible_sites(['ADMIN', 'SECURITY_SUBSITE_GROUP'], true); + $subsiteMap = $subsites->map(); + + // Prevent XSS injection + $subsiteMap = Convert::raw2xml($subsiteMap); + + // Interface is different if you have the rights to modify subsite group values on + // all subsites + if (isset($subsiteMap[0])) { + $fields->addFieldToTab("Root.Subsites", new OptionsetField("AccessAllSubsites", + _t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'), + [ + 1 => _t('GroupSubsites.ACCESSALL', "All subsites"), + 0 => _t('GroupSubsites.ACCESSONLY', "Only these subsites"), + ] + )); + + unset($subsiteMap[0]); + $fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites", "", + $subsiteMap)); + + } else { + if (sizeof($subsiteMap) <= 1) { + $fields->addFieldToTab("Root.Subsites", new ReadonlyField("SubsitesHuman", + _t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'), + reset($subsiteMap))); + } else { + $fields->addFieldToTab("Root.Subsites", new CheckboxSetField("Subsites", + _t('GroupSubsites.ACCESSRADIOTITLE', 'Give this group access to'), + $subsiteMap)); + } + } + } + } + + /** + * If this group belongs to a subsite, + * append the subsites title to the group title + * to make it easy to distinguish in the tree-view + * of the security admin interface. + */ + function alternateTreeTitle() + { + if ($this->owner->AccessAllSubsites) { + $title = _t('GroupSubsites.GlobalGroup', 'global group'); + return htmlspecialchars($this->owner->Title, ENT_QUOTES) . ' (' . $title . ')'; + } else { + $subsites = Convert::raw2xml(implode(", ", $this->owner->Subsites()->column('Title'))); + return htmlspecialchars($this->owner->Title) . " ($subsites)"; + } + } + + /** + * Update any requests to limit the results to the current site + */ + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) + { + if (Subsite::$disable_subsite_filter) { + return; + } + if (Cookie::get('noSubsiteFilter') == 'true') { + return; + } + + // If you're querying by ID, ignore the sub-site - this is a bit ugly... + if (!$query->filtersOnID()) { + + /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; + else */ + $subsiteID = (int)Subsite::currentSubsiteID(); + + // Don't filter by Group_Subsites if we've already done that + $hasGroupSubsites = false; + foreach ($query->getFrom() as $item) { + if ((is_array($item) && strpos($item['table'], + 'Group_Subsites') !== false) || (!is_array($item) && strpos($item, + 'Group_Subsites') !== false) + ) { + $hasGroupSubsites = true; + break; + } + } + + if (!$hasGroupSubsites) { + if ($subsiteID) { + $query->addLeftJoin("Group_Subsites", "\"Group_Subsites\".\"GroupID\" = \"Group\".\"ID\" AND \"Group_Subsites\".\"SubsiteID\" = $subsiteID"); - $query->addWhere("(\"Group_Subsites\".\"SubsiteID\" IS NOT NULL OR + $query->addWhere("(\"Group_Subsites\".\"SubsiteID\" IS NOT NULL OR \"Group\".\"AccessAllSubsites\" = 1)"); - } else { - $query->addWhere("\"Group\".\"AccessAllSubsites\" = 1"); - } - } - - // WORKAROUND for databases that complain about an ORDER BY when the column wasn't selected (e.g. SQL Server) - $select=$query->getSelect(); - if(isset($select[0]) && !$select[0] == 'COUNT(*)') { - $query->orderby = "\"AccessAllSubsites\" DESC" . ($query->orderby ? ', ' : '') . $query->orderby; - } - } - } - - function onBeforeWrite() { - // New record test approximated by checking whether the ID has changed. - // Note also that the after write test is only used when we're *not* on a subsite - if($this->owner->isChanged('ID') && !Subsite::currentSubsiteID()) { - $this->owner->AccessAllSubsites = 1; - } - } - - function onAfterWrite() { - // New record test approximated by checking whether the ID has changed. - // Note also that the after write test is only used when we're on a subsite - if($this->owner->isChanged('ID') && $currentSubsiteID = Subsite::currentSubsiteID()) { - $subsites = $this->owner->Subsites(); - $subsites->add($currentSubsiteID); - } - } - - function alternateCanEdit() { - // Find the sites that this group belongs to and the sites where we have appropriate perm. - $accessibleSites = Subsite::accessible_sites('CMS_ACCESS_SecurityAdmin')->column('ID'); - $linkedSites = $this->owner->Subsites()->column('ID'); - - // We are allowed to access this site if at we have CMS_ACCESS_SecurityAdmin permission on - // at least one of the sites - return (bool)array_intersect($accessibleSites, $linkedSites); - } - - function providePermissions() { - return array( - 'SECURITY_SUBSITE_GROUP' => array( - 'name' => _t('GroupSubsites.MANAGE_SUBSITES', 'Manage subsites for groups'), - 'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'), - 'help' => _t('GroupSubsites.MANAGE_SUBSITES_HELP', 'Ability to limit the permissions for a group to one or more subsites.'), - 'sort' => 200 - ) - ); - } + } else { + $query->addWhere("\"Group\".\"AccessAllSubsites\" = 1"); + } + } + + // WORKAROUND for databases that complain about an ORDER BY when the column wasn't selected (e.g. SQL Server) + $select = $query->getSelect(); + if (isset($select[0]) && !$select[0] == 'COUNT(*)') { + $query->orderby = "\"AccessAllSubsites\" DESC" . ($query->orderby ? ', ' : '') . $query->orderby; + } + } + } + + function onBeforeWrite() + { + // New record test approximated by checking whether the ID has changed. + // Note also that the after write test is only used when we're *not* on a subsite + if ($this->owner->isChanged('ID') && !Subsite::currentSubsiteID()) { + $this->owner->AccessAllSubsites = 1; + } + } + + function onAfterWrite() + { + // New record test approximated by checking whether the ID has changed. + // Note also that the after write test is only used when we're on a subsite + if ($this->owner->isChanged('ID') && $currentSubsiteID = Subsite::currentSubsiteID()) { + $subsites = $this->owner->Subsites(); + $subsites->add($currentSubsiteID); + } + } + + function alternateCanEdit() + { + // Find the sites that this group belongs to and the sites where we have appropriate perm. + $accessibleSites = Subsite::accessible_sites('CMS_ACCESS_SecurityAdmin')->column('ID'); + $linkedSites = $this->owner->Subsites()->column('ID'); + + // We are allowed to access this site if at we have CMS_ACCESS_SecurityAdmin permission on + // at least one of the sites + return (bool)array_intersect($accessibleSites, $linkedSites); + } + + function providePermissions() + { + return [ + 'SECURITY_SUBSITE_GROUP' => [ + 'name' => _t('GroupSubsites.MANAGE_SUBSITES', 'Manage subsites for groups'), + 'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'), + 'help' => _t('GroupSubsites.MANAGE_SUBSITES_HELP', + 'Ability to limit the permissions for a group to one or more subsites.'), + 'sort' => 200 + ] + ]; + } } diff --git a/code/extensions/LeftAndMainSubsites.php b/code/extensions/LeftAndMainSubsites.php index 78c628ad..810fd8a0 100644 --- a/code/extensions/LeftAndMainSubsites.php +++ b/code/extensions/LeftAndMainSubsites.php @@ -3,21 +3,21 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\View\Requirements; +use SilverStripe\Admin\CMSMenu; +use SilverStripe\Control\Controller; +use SilverStripe\Control\Session; +use SilverStripe\Core\Config\Config; use SilverStripe\Core\Convert; +use SilverStripe\Core\Extension; use SilverStripe\Forms\HiddenField; -use SilverStripe\Security\Member; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataObject; -use SilverStripe\Core\Config\Config; -use SilverStripe\View\ArrayData; +use SilverStripe\Security\Member; use SilverStripe\Security\Permission; -use SilverStripe\Control\Session; -use SilverStripe\Admin\CMSMenu; use SilverStripe\Security\Security; -use SilverStripe\Control\Controller; -use SilverStripe\Core\Extension; use SilverStripe\Subsites\Model\Subsite; +use SilverStripe\View\ArrayData; +use SilverStripe\View\Requirements; /** @@ -25,303 +25,335 @@ * * @package subsites */ -class LeftAndMainSubsites extends Extension { - - private static $allowed_actions = array('CopyToSubsite'); - - /** - * Normally SubsiteID=0 on a DataObject means it is only accessible from the special "main site". - * However in some situations SubsiteID=0 will be understood as a "globally accessible" object in which - * case this property is set to true (i.e. in AssetAdmin). - */ - private static $treats_subsite_0_as_global = false; - - function init() { - Requirements::css('subsites/css/LeftAndMain_Subsites.css'); - Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js'); - Requirements::javascript('subsites/javascript/VirtualPage_Subsites.js'); - } - - /** - * Set the title of the CMS tree - */ - function getCMSTreeTitle() { - $subsite = Subsite::currentSubSite(); - return $subsite ? Convert::raw2xml($subsite->Title) : _t('LeftAndMain.SITECONTENTLEFT'); - } - - function updatePageOptions(&$fields) { - $fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID())); - } - - /** - * Find all subsites accessible for current user on this controller. - * - * @return ArrayList of {@link Subsite} instances. - */ - function sectionSites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) { - if($mainSiteTitle == 'Main site') { - $mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site'); - } - - // Rationalise member arguments - if(!$member) $member = Member::currentUser(); - if(!$member) return new ArrayList(); - if(!is_object($member)) $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); - - // Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires - // us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView. - $codes = array(); - $extraCodes = Config::inst()->get($this->owner->class, 'required_permission_codes'); - if($extraCodes !== false) { - if($extraCodes) $codes = array_merge($codes, (array)$extraCodes); - else $codes[] = "CMS_ACCESS_{$this->owner->class}"; - } else { - // Check overriden - all subsites accessible. - return Subsite::all_sites(); - } - - // Find subsites satisfying all permissions for the Member. - $codesPerSite = array(); - $sitesArray = array(); - foreach ($codes as $code) { - $sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member); - foreach ($sites as $site) { - // Build the structure for checking how many codes match. - $codesPerSite[$site->ID][$code] = true; - - // Retain Subsite objects for later. - $sitesArray[$site->ID] = $site; - } - } - - // Find sites that satisfy all codes conjuncitvely. - $accessibleSites = new ArrayList(); - foreach ($codesPerSite as $siteID => $siteCodes) { - if (count($siteCodes)==count($codes)) { - $accessibleSites->push($sitesArray[$siteID]); - } - } - - return $accessibleSites; - } - - /* - * Returns a list of the subsites accessible to the current user. - * It's enough for any section to be accessible for the section to be included. - */ - public function Subsites() { - return Subsite::all_accessible_sites(); - } - - /* - * Generates a list of subsites with the data needed to - * produce a dropdown site switcher - * @return ArrayList - */ - - public function ListSubsites(){ - $list = $this->Subsites(); - $currentSubsiteID = Subsite::currentSubsiteID(); - - if($list == null || $list->Count() == 1 && $list->First()->DefaultSite == true){ - return false; - } - - Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js'); - - $output = new ArrayList(); - - foreach($list as $subsite) { - $CurrentState = $subsite->ID == $currentSubsiteID ? 'selected' : ''; - - $output->push(new ArrayData(array( - 'CurrentState' => $CurrentState, - 'ID' => $subsite->ID, - 'Title' => Convert::raw2xml($subsite->Title) - ))); - } - - return $output; - } - - public function alternateMenuDisplayCheck($controllerName) { - if(!class_exists($controllerName)){ - return false; - } - - // Check subsite support. - if(Subsite::currentSubsiteID() == 0){ - // Main site always supports everything. - return true; - } else { - $controller = singleton($controllerName); - if($controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu()){ - return true; - } - } - - // It's not necessary to check access permissions here. Framework calls canView on the controller, - // which in turn uses the Permission API which is augmented by our GroupSubsites. - - return false; - } - - public function CanAddSubsites() { - return Permission::check("ADMIN", "any", null, "all"); - } - - /** - * Helper for testing if the subsite should be adjusted. - */ - public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID) { - if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID==0) return false; - if ($recordSubsiteID!=$currentSubsiteID) return true; - return false; - } - - /** - * Check if the current controller is accessible for this user on this subsite. - */ - function canAccess() { - // Admin can access everything, no point in checking. - $member = Member::currentUser(); - if($member && - ( - Permission::checkMember($member, 'ADMIN') || // 'Full administrative rights' in SecurityAdmin - Permission::checkMember($member, 'CMS_ACCESS_LeftAndMain') // 'Access to all CMS sections' in SecurityAdmin - )) { - return true; - } - - // Check if we have access to current section on the current subsite. - $accessibleSites = $this->owner->sectionSites(true, "Main site", $member); - if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) { - // Current section can be accessed on the current site, all good. - return true; - } - - return false; - } - - /** - * Prevent accessing disallowed resources. This happens after onBeforeInit has executed, - * so all redirections should've already taken place. - */ - public function alternateAccessCheck() { - return $this->owner->canAccess(); - } - - /** - * Redirect the user to something accessible if the current section/subsite is forbidden. - * - * This is done via onBeforeInit as it needs to be done before the LeftAndMain::init has a - * chance to forbids access via alternateAccessCheck. - * - * If we need to change the subsite we force the redirection to /admin/ so the frontend is - * fully re-synchronised with the internal session. This is better than risking some panels - * showing data from another subsite. - */ - public function onBeforeInit() { - // We are accessing the CMS, so we need to let Subsites know we will be using the session. - Subsite::$use_session_subsiteid = true; - - // FIRST, check if we need to change subsites due to the URL. - - // Catch forced subsite changes that need to cause CMS reloads. - if(isset($_GET['SubsiteID'])) { - // Clear current page when subsite changes (or is set for the first time) - if(!Session::get('SubsiteID') || $_GET['SubsiteID'] != Session::get('SubsiteID')) { - Session::clear("{$this->owner->class}.currentPage"); - } - - // Update current subsite in session - Subsite::changeSubsite($_GET['SubsiteID']); - - //Redirect to clear the current page - if ($this->owner->canView(Member::currentUser())) { - //Redirect to clear the current page - return $this->owner->redirect($this->owner->Link()); - } - //Redirect to the default CMS section - return $this->owner->redirect('admin/'); - } - - // Automatically redirect the session to appropriate subsite when requesting a record. - // This is needed to properly initialise the session in situations where someone opens the CMS via a link. - $record = $this->owner->currentPage(); - if($record && isset($record->SubsiteID) && is_numeric($record->SubsiteID) && isset($this->owner->urlParams['ID'])) { - - if ($this->shouldChangeSubsite($this->owner->class, $record->SubsiteID, Subsite::currentSubsiteID())) { - // Update current subsite in session - Subsite::changeSubsite($record->SubsiteID); - - if ($this->owner->canView(Member::currentUser())) { - //Redirect to clear the current page - return $this->owner->redirect($this->owner->Link()); - } - //Redirect to the default CMS section - return $this->owner->redirect('admin/'); - } - - } - - // SECOND, check if we need to change subsites due to lack of permissions. - - if (!$this->owner->canAccess()) { - - $member = Member::currentUser(); - - // Current section is not accessible, try at least to stick to the same subsite. - $menu = CMSMenu::get_menu_items(); - foreach($menu as $candidate) { - if($candidate->controller && $candidate->controller!=$this->owner->class) { - - $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member); - if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) { - // Section is accessible, redirect there. - return $this->owner->redirect(singleton($candidate->controller)->Link()); - } - } - } - - // If no section is available, look for other accessible subsites. - foreach($menu as $candidate) { - if($candidate->controller) { - $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member); - if ($accessibleSites->count()) { - Subsite::changeSubsite($accessibleSites->First()->ID); - return $this->owner->redirect(singleton($candidate->controller)->Link()); - } - } - } - - // We have not found any accessible section or subsite. User should be denied access. - return Security::permissionFailure($this->owner); - - } - - // Current site is accessible. Allow through. - return; - } - - function augmentNewSiteTreeItem(&$item) { - $item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID(); - } - - function onAfterSave($record) { - if($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) { - $this->owner->response->addHeader('X-Status', rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.'))); - } - } - - function copytosubsite($data, $form) { - $page = DataObject::get_by_id('SilverStripe\\CMS\\Model\\SiteTree', $data['ID']); - $subsite = DataObject::get_by_id(Subsite::class, $data['CopyToSubsiteID']); - $newPage = $page->duplicateToSubsite($subsite->ID, true); - $response = $this->owner->getResponse(); - $response->addHeader('X-Reload', true); - return $this->owner->redirect(Controller::join_links($this->owner->Link('show'), $newPage->ID)); - } +class LeftAndMainSubsites extends Extension +{ + + private static $allowed_actions = ['CopyToSubsite']; + + /** + * Normally SubsiteID=0 on a DataObject means it is only accessible from the special "main site". + * However in some situations SubsiteID=0 will be understood as a "globally accessible" object in which + * case this property is set to true (i.e. in AssetAdmin). + */ + private static $treats_subsite_0_as_global = false; + + function init() + { + Requirements::css('subsites/css/LeftAndMain_Subsites.css'); + Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js'); + Requirements::javascript('subsites/javascript/VirtualPage_Subsites.js'); + } + + /** + * Set the title of the CMS tree + */ + function getCMSTreeTitle() + { + $subsite = Subsite::currentSubSite(); + return $subsite ? Convert::raw2xml($subsite->Title) : _t('LeftAndMain.SITECONTENTLEFT'); + } + + function updatePageOptions(&$fields) + { + $fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID())); + } + + /** + * Find all subsites accessible for current user on this controller. + * + * @return ArrayList of {@link Subsite} instances. + */ + function sectionSites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) + { + if ($mainSiteTitle == 'Main site') { + $mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site'); + } + + // Rationalise member arguments + if (!$member) { + $member = Member::currentUser(); + } + if (!$member) { + return new ArrayList(); + } + if (!is_object($member)) { + $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); + } + + // Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires + // us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView. + $codes = []; + $extraCodes = Config::inst()->get($this->owner->class, 'required_permission_codes'); + if ($extraCodes !== false) { + if ($extraCodes) { + $codes = array_merge($codes, (array)$extraCodes); + } else { + $codes[] = "CMS_ACCESS_{$this->owner->class}"; + } + } else { + // Check overriden - all subsites accessible. + return Subsite::all_sites(); + } + + // Find subsites satisfying all permissions for the Member. + $codesPerSite = []; + $sitesArray = []; + foreach ($codes as $code) { + $sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member); + foreach ($sites as $site) { + // Build the structure for checking how many codes match. + $codesPerSite[$site->ID][$code] = true; + + // Retain Subsite objects for later. + $sitesArray[$site->ID] = $site; + } + } + + // Find sites that satisfy all codes conjuncitvely. + $accessibleSites = new ArrayList(); + foreach ($codesPerSite as $siteID => $siteCodes) { + if (count($siteCodes) == count($codes)) { + $accessibleSites->push($sitesArray[$siteID]); + } + } + + return $accessibleSites; + } + + /* + * Returns a list of the subsites accessible to the current user. + * It's enough for any section to be accessible for the section to be included. + */ + public function Subsites() + { + return Subsite::all_accessible_sites(); + } + + /* + * Generates a list of subsites with the data needed to + * produce a dropdown site switcher + * @return ArrayList + */ + + public function ListSubsites() + { + $list = $this->Subsites(); + $currentSubsiteID = Subsite::currentSubsiteID(); + + if ($list == null || $list->Count() == 1 && $list->First()->DefaultSite == true) { + return false; + } + + Requirements::javascript('subsites/javascript/LeftAndMain_Subsites.js'); + + $output = new ArrayList(); + + foreach ($list as $subsite) { + $CurrentState = $subsite->ID == $currentSubsiteID ? 'selected' : ''; + + $output->push(new ArrayData([ + 'CurrentState' => $CurrentState, + 'ID' => $subsite->ID, + 'Title' => Convert::raw2xml($subsite->Title) + ])); + } + + return $output; + } + + public function alternateMenuDisplayCheck($controllerName) + { + if (!class_exists($controllerName)) { + return false; + } + + // Check subsite support. + if (Subsite::currentSubsiteID() == 0) { + // Main site always supports everything. + return true; + } else { + $controller = singleton($controllerName); + if ($controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu()) { + return true; + } + } + + // It's not necessary to check access permissions here. Framework calls canView on the controller, + // which in turn uses the Permission API which is augmented by our GroupSubsites. + + return false; + } + + public function CanAddSubsites() + { + return Permission::check("ADMIN", "any", null, "all"); + } + + /** + * Helper for testing if the subsite should be adjusted. + */ + public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID) + { + if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID == 0) { + return false; + } + if ($recordSubsiteID != $currentSubsiteID) { + return true; + } + return false; + } + + /** + * Check if the current controller is accessible for this user on this subsite. + */ + function canAccess() + { + // Admin can access everything, no point in checking. + $member = Member::currentUser(); + if ($member && + ( + Permission::checkMember($member, 'ADMIN') || // 'Full administrative rights' in SecurityAdmin + Permission::checkMember($member, + 'CMS_ACCESS_LeftAndMain') // 'Access to all CMS sections' in SecurityAdmin + ) + ) { + return true; + } + + // Check if we have access to current section on the current subsite. + $accessibleSites = $this->owner->sectionSites(true, "Main site", $member); + if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) { + // Current section can be accessed on the current site, all good. + return true; + } + + return false; + } + + /** + * Prevent accessing disallowed resources. This happens after onBeforeInit has executed, + * so all redirections should've already taken place. + */ + public function alternateAccessCheck() + { + return $this->owner->canAccess(); + } + + /** + * Redirect the user to something accessible if the current section/subsite is forbidden. + * + * This is done via onBeforeInit as it needs to be done before the LeftAndMain::init has a + * chance to forbids access via alternateAccessCheck. + * + * If we need to change the subsite we force the redirection to /admin/ so the frontend is + * fully re-synchronised with the internal session. This is better than risking some panels + * showing data from another subsite. + */ + public function onBeforeInit() + { + // We are accessing the CMS, so we need to let Subsites know we will be using the session. + Subsite::$use_session_subsiteid = true; + + // FIRST, check if we need to change subsites due to the URL. + + // Catch forced subsite changes that need to cause CMS reloads. + if (isset($_GET['SubsiteID'])) { + // Clear current page when subsite changes (or is set for the first time) + if (!Session::get('SubsiteID') || $_GET['SubsiteID'] != Session::get('SubsiteID')) { + Session::clear("{$this->owner->class}.currentPage"); + } + + // Update current subsite in session + Subsite::changeSubsite($_GET['SubsiteID']); + + //Redirect to clear the current page + if ($this->owner->canView(Member::currentUser())) { + //Redirect to clear the current page + return $this->owner->redirect($this->owner->Link()); + } + //Redirect to the default CMS section + return $this->owner->redirect('admin/'); + } + + // Automatically redirect the session to appropriate subsite when requesting a record. + // This is needed to properly initialise the session in situations where someone opens the CMS via a link. + $record = $this->owner->currentPage(); + if ($record && isset($record->SubsiteID) && is_numeric($record->SubsiteID) && isset($this->owner->urlParams['ID'])) { + + if ($this->shouldChangeSubsite($this->owner->class, $record->SubsiteID, Subsite::currentSubsiteID())) { + // Update current subsite in session + Subsite::changeSubsite($record->SubsiteID); + + if ($this->owner->canView(Member::currentUser())) { + //Redirect to clear the current page + return $this->owner->redirect($this->owner->Link()); + } + //Redirect to the default CMS section + return $this->owner->redirect('admin/'); + } + + } + + // SECOND, check if we need to change subsites due to lack of permissions. + + if (!$this->owner->canAccess()) { + + $member = Member::currentUser(); + + // Current section is not accessible, try at least to stick to the same subsite. + $menu = CMSMenu::get_menu_items(); + foreach ($menu as $candidate) { + if ($candidate->controller && $candidate->controller != $this->owner->class) { + + $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member); + if ($accessibleSites->count() && $accessibleSites->find('ID', Subsite::currentSubsiteID())) { + // Section is accessible, redirect there. + return $this->owner->redirect(singleton($candidate->controller)->Link()); + } + } + } + + // If no section is available, look for other accessible subsites. + foreach ($menu as $candidate) { + if ($candidate->controller) { + $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member); + if ($accessibleSites->count()) { + Subsite::changeSubsite($accessibleSites->First()->ID); + return $this->owner->redirect(singleton($candidate->controller)->Link()); + } + } + } + + // We have not found any accessible section or subsite. User should be denied access. + return Security::permissionFailure($this->owner); + + } + + // Current site is accessible. Allow through. + return; + } + + function augmentNewSiteTreeItem(&$item) + { + $item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID(); + } + + function onAfterSave($record) + { + if ($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) { + $this->owner->response->addHeader('X-Status', + rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.'))); + } + } + + function copytosubsite($data, $form) + { + $page = DataObject::get_by_id('SilverStripe\\CMS\\Model\\SiteTree', $data['ID']); + $subsite = DataObject::get_by_id(Subsite::class, $data['CopyToSubsiteID']); + $newPage = $page->duplicateToSubsite($subsite->ID, true); + $response = $this->owner->getResponse(); + $response->addHeader('X-Reload', true); + return $this->owner->redirect(Controller::join_links($this->owner->Link('show'), $newPage->ID)); + } } diff --git a/code/extensions/SiteConfigSubsites.php b/code/extensions/SiteConfigSubsites.php index acd6db45..ea205849 100644 --- a/code/extensions/SiteConfigSubsites.php +++ b/code/extensions/SiteConfigSubsites.php @@ -3,60 +3,75 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\ORM\Queries\SQLSelect; -use SilverStripe\ORM\DataQuery; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\HiddenField; use SilverStripe\ORM\DataExtension; -use SilverStripe\Subsites\Model\Subsite; +use SilverStripe\ORM\DataQuery; +use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\SiteConfig\SiteConfig; - +use SilverStripe\Subsites\Model\Subsite; /** * Extension for the SiteConfig object to add subsites support */ -class SiteConfigSubsites extends DataExtension { - - private static $has_one = array( - 'Subsite' => Subsite::class, // The subsite that this page belongs to - ); - - /** - * Update any requests to limit the results to the current site - */ - public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { - if(Subsite::$disable_subsite_filter) return; - - // If you're querying by ID, ignore the sub-site - this is a bit ugly... - if($query->filtersOnID()) return; - $regexp = '/^(.*\.)?("|`)?SubsiteID("|`)?\s?=/'; - foreach($query->getWhereParameterised($parameters) as $predicate) { - if(preg_match($regexp, $predicate)) return; - } - - /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; - else */$subsiteID = (int)Subsite::currentSubsiteID(); - - $froms=$query->getFrom(); - $froms=array_keys($froms); - $tableName = array_shift($froms); - if($tableName != SiteConfig::class) return; - $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)"); - } - - function onBeforeWrite() { - if((!is_numeric($this->owner->ID) || !$this->owner->ID) && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID(); - } - - /** - * Return a piece of text to keep DataObject cache keys appropriately specific - */ - function cacheKeyComponent() { - return 'subsite-'.Subsite::currentSubsiteID(); - } - - function updateCMSFields(FieldList $fields) { - $fields->push(new HiddenField('SubsiteID','SubsiteID', Subsite::currentSubsiteID())); - } +class SiteConfigSubsites extends DataExtension +{ + + private static $has_one = [ + 'Subsite' => Subsite::class, // The subsite that this page belongs to + ]; + + /** + * Update any requests to limit the results to the current site + */ + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) + { + if (Subsite::$disable_subsite_filter) { + return; + } + + // If you're querying by ID, ignore the sub-site - this is a bit ugly... + if ($query->filtersOnID()) { + return; + } + $regexp = '/^(.*\.)?("|`)?SubsiteID("|`)?\s?=/'; + foreach ($query->getWhereParameterised($parameters) as $predicate) { + if (preg_match($regexp, $predicate)) { + return; + } + } + + /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; + else */ + $subsiteID = (int)Subsite::currentSubsiteID(); + + $froms = $query->getFrom(); + $froms = array_keys($froms); + $tableName = array_shift($froms); + if ($tableName != SiteConfig::class) { + return; + } + $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)"); + } + + function onBeforeWrite() + { + if ((!is_numeric($this->owner->ID) || !$this->owner->ID) && !$this->owner->SubsiteID) { + $this->owner->SubsiteID = Subsite::currentSubsiteID(); + } + } + + /** + * Return a piece of text to keep DataObject cache keys appropriately specific + */ + function cacheKeyComponent() + { + return 'subsite-' . Subsite::currentSubsiteID(); + } + + function updateCMSFields(FieldList $fields) + { + $fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID())); + } } diff --git a/code/extensions/SiteTreeSubsites.php b/code/extensions/SiteTreeSubsites.php index ffcbc749..8885e010 100644 --- a/code/extensions/SiteTreeSubsites.php +++ b/code/extensions/SiteTreeSubsites.php @@ -3,364 +3,418 @@ namespace SilverStripe\Subsites\Extensions; -use SilverStripe\ORM\Queries\SQLSelect; -use SilverStripe\ORM\DataQuery; -use SilverStripe\Forms\FieldList; -use SilverStripe\Forms\DropdownField; -use SilverStripe\Forms\InlineFormAction; -use SilverStripe\Core\Config\Config; -use SilverStripe\Control\Director; +use Page; +use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Control\Controller; -use SilverStripe\ORM\DataObject; -use SilverStripe\SiteConfig\SiteConfig; -use SilverStripe\Security\Member; +use SilverStripe\Control\Director; use SilverStripe\Control\HTTP; +use SilverStripe\Core\Config\Config; use SilverStripe\Core\Convert; +use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\InlineFormAction; use SilverStripe\ORM\DataExtension; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\DataQuery; +use SilverStripe\ORM\Queries\SQLSelect; +use SilverStripe\Security\Member; +use SilverStripe\SiteConfig\SiteConfig; use SilverStripe\Subsites\Model\Subsite; -use Page; -use SilverStripe\CMS\Model\SiteTree; - /** * Extension for the SiteTree object to add subsites support */ -class SiteTreeSubsites extends DataExtension { - - private static $has_one = array( - 'Subsite' => Subsite::class, // The subsite that this page belongs to - ); - - private static $many_many = array( - 'CrossSubsiteLinkTracking' => 'SilverStripe\\CMS\\Model\\SiteTree' // Stored separately, as the logic for URL rewriting is different - ); - - private static $many_many_extraFields = array( - "CrossSubsiteLinkTracking" => array("FieldName" => "Varchar") - ); - - function isMainSite() { - if($this->owner->SubsiteID == 0) return true; - return false; - } - - /** - * Update any requests to limit the results to the current site - */ - public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { - if(Subsite::$disable_subsite_filter) return; - if($dataQuery->getQueryParam('Subsite.filter') === false) return; - - // If you're querying by ID, ignore the sub-site - this is a bit ugly... - // if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) { - if($query->filtersOnID()) return; - - if (Subsite::$force_subsite) $subsiteID = Subsite::$force_subsite; - else { - /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; - else */$subsiteID = (int)Subsite::currentSubsiteID(); - } - - // The foreach is an ugly way of getting the first key :-) - foreach($query->getFrom() as $tableName => $info) { - // The tableName should be SiteTree or SiteTree_Live... - if(strpos($tableName,SiteTree::class) === false) break; - $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)"); - break; - } - } - - function onBeforeWrite() { - if(!$this->owner->ID && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID(); - - parent::onBeforeWrite(); - } - - function updateCMSFields(FieldList $fields) { - $subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain"); - $subsitesMap = array(); - if($subsites && $subsites->Count()) { - $subsitesMap = $subsites->map('ID', 'Title')->toArray(); - unset($subsitesMap[$this->owner->SubsiteID]); - } - - // Master page edit field (only allowed from default subsite to avoid inconsistent relationships) - $isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite; - if($isDefaultSubsite && $subsitesMap) { - $fields->addFieldToTab( - 'Root.Main', - new DropdownField( - "CopyToSubsiteID", - _t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"), - $subsitesMap, - '' - ) - ); - $fields->addFieldToTab( - 'Root.Main', - $copyAction = new InlineFormAction( - "copytosubsite", - _t('SiteTreeSubsites.CopyAction', "Copy") - ) - ); - } - - // replace readonly link prefix - $subsite = $this->owner->Subsite(); - $nested_urls_enabled = Config::inst()->get('SilverStripe\\CMS\\Model\\SiteTree', 'nested_urls'); - if($subsite && $subsite->ID) { - $baseUrl = Director::protocol() . $subsite->domain() . '/'; - $baseLink = Controller::join_links ( - $baseUrl, - ($nested_urls_enabled && $this->owner->ParentID ? $this->owner->Parent()->RelativeLink(true) : null) - ); - - $urlsegment = $fields->dataFieldByName('URLSegment'); - $urlsegment->setURLPrefix($baseLink); - } - } - - function alternateSiteConfig() { - if(!$this->owner->SubsiteID) return false; - $sc = DataObject::get_one('SilverStripe\\SiteConfig\\SiteConfig', '"SubsiteID" = ' . $this->owner->SubsiteID); - if(!$sc) { - $sc = new SiteConfig(); - $sc->SubsiteID = $this->owner->SubsiteID; - $sc->Title = _t('Subsite.SiteConfigTitle','Your Site Name'); - $sc->Tagline = _t('Subsite.SiteConfigSubtitle','Your tagline here'); - $sc->write(); - } - return $sc; - } - - /** - * Only allow editing of a page if the member satisfies one of the following conditions: - * - Is in a group which has access to the subsite this page belongs to - * - Is in a group with edit permissions on the "main site" - * - * @return boolean - */ - function canEdit($member = null) { - - if(!$member) $member = Member::currentUser(); - - // Find the sites that this user has access to - $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain',true,'all',$member)->column('ID'); - - if (!is_null($this->owner->SubsiteID)) { - $subsiteID = $this->owner->SubsiteID; - } else { - // The relationships might not be available during the record creation when using a GridField. - // In this case the related objects will have empty fields, and SubsiteID will not be available. - // - // We do the second best: fetch the likely SubsiteID from the session. The drawback is this might - // make it possible to force relations to point to other (forbidden) subsites. - $subsiteID = Subsite::currentSubsiteID(); - } - - // Return true if they have access to this object's site - if(!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) return false; - } - - /** - * @return boolean - */ - function canDelete($member = null) { - if(!$member && $member !== FALSE) $member = Member::currentUser(); - - return $this->canEdit($member); - } - - /** - * @return boolean - */ - function canAddChildren($member = null) { - if(!$member && $member !== FALSE) $member = Member::currentUser(); - - return $this->canEdit($member); - } - - /** - * @return boolean - */ - function canPublish($member = null) { - if(!$member && $member !== FALSE) $member = Member::currentUser(); - - return $this->canEdit($member); - } - - /** - * Does the basic duplication, but doesn't write anything - * this means we can subclass this easier and do more complex - * relation duplication. - */ - public function duplicateToSubsitePrep($subsiteID) { - if (is_object($subsiteID)) { - $subsiteID = $subsiteID->ID; - } - - $oldSubsite = Subsite::currentSubsiteID(); - if ($subsiteID) { - Subsite::changeSubsite($subsiteID); - } else { - $subsiteID = $oldSubsite; - } - // doesn't write as we need to reset the SubsiteID, ParentID etc - $clone = $this->owner->duplicate(false); - $clone->CheckedPublicationDifferences = $clone->AddedToStage = true; - $subsiteID = ($subsiteID ? $subsiteID : $oldSubsite); - $clone->SubsiteID = $subsiteID; - // We have no idea what the parentID should be, so as a workaround use the url-segment and subsite ID - if ($this->owner->Parent()) { - $parentSeg = $this->owner->Parent()->URLSegment; - $newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first(); - if ($newParentPage) { - $clone->ParentID = $newParentPage->ID; - } else { - // reset it to the top level, so the user can decide where to put it - $clone->ParentID = 0; - } - } - // MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module - $clone->MasterPageID = $this->owner->ID; - return $clone; - } - - /** - * Create a duplicate of this page and save it to another subsite - * @param $subsiteID int|Subsite The Subsite to copy to, or its ID - */ - public function duplicateToSubsite($subsiteID = null) { - $clone = $this->owner->duplicateToSubsitePrep($subsiteID); - $clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner); - $clone->write(); - $clone->duplicateSubsiteRelations($this->owner); - // new extension hooks which happens after write, - // onAfterDuplicate isn't reliable due to - // https://github.com/silverstripe/silverstripe-cms/issues/1253 - $clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner); - return $clone; - } - - /** - * Duplicate relations using a static property to define - * which ones we want to duplicate - * - * It may be that some relations are not diostinct to sub site so can stay - * whereas others may need to be duplicated - * - */ - public function duplicateSubsiteRelations($originalPage) { - $thisClass = $originalPage->ClassName; - $relations = Config::inst()->get($thisClass, 'duplicate_to_subsite_relations'); - - if($relations && !empty($relations)) { - foreach($relations as $relation) { - $items = $originalPage->$relation(); - foreach($items as $item) { - $duplicateItem = $item->duplicate(false); - $duplicateItem->{$thisClass.'ID'} = $this->owner->ID; - $duplicateItem->write(); - } - } - } - } - - /** - * Called by ContentController::init(); - */ - static function contentcontrollerInit($controller) { - $subsite = Subsite::currentSubsite(); - - if($subsite && $subsite->Theme){ - Config::modify()->set('SilverStripe\\View\\SSViewer', 'theme', Subsite::currentSubsite()->Theme); - } - } - - function alternateAbsoluteLink() { - // Generate the existing absolute URL and replace the domain with the subsite domain. - // This helps deal with Link() returning an absolute URL. - $url = Director::absoluteURL($this->owner->Link()); - if($this->owner->SubsiteID) { - $url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url); - } - return $url; - } - - /** - * Use the CMS domain for iframed CMS previews to prevent single-origin violations - * and SSL cert problems. - */ - function alternatePreviewLink($action = null) { - $url = Director::absoluteURL($this->owner->Link()); - if($this->owner->SubsiteID) { - $url = HTTP::setGetVar('SubsiteID', $this->owner->SubsiteID, $url); - } - return $url; - } - - /** - * Inject the subsite ID into the content so it can be used by frontend scripts. - */ - function MetaTags(&$tags) { - if($this->owner->SubsiteID) { - $tags .= "owner->SubsiteID . "\" />\n"; - } - - return $tags; - } - - function augmentSyncLinkTracking() { - // Set LinkTracking appropriately - $links = HTTP::getLinksIn($this->owner->Content); - $linkedPages = array(); - - if($links) foreach($links as $link) { - if(substr($link, 0, strlen('http://')) == 'http://') { - $withoutHttp = substr($link, strlen('http://')); - if(strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) { - $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/')); - $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1); - - $subsiteID = Subsite::getSubsiteIDForDomain($domain); - if($subsiteID == 0) continue; // We have no idea what the domain for the main site is, so cant track links to it - - $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; - Subsite::disable_subsite_filter(true); - $candidatePage = DataObject::get_one("SilverStripe\\CMS\\Model\\SiteTree", "\"URLSegment\" = '" . Convert::raw2sql(urldecode( $rest)) . "' AND \"SubsiteID\" = " . $subsiteID, false); - Subsite::disable_subsite_filter($origDisableSubsiteFilter); - - if($candidatePage) { - $linkedPages[] = $candidatePage->ID; - } else { - $this->owner->HasBrokenLink = true; - } - } - } - } - - $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages); - } - - /** - * Return a piece of text to keep DataObject cache keys appropriately specific - */ - function cacheKeyComponent() { - return 'subsite-'.Subsite::currentSubsiteID(); - } - - /** - * @param Member - * @return boolean|null - */ - function canCreate($member = null) { - // Typically called on a singleton, so we're not using the Subsite() relation - $subsite = Subsite::currentSubsite(); - if($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) { - $blacklisted = explode(',', $subsite->PageTypeBlacklist); - // All subclasses need to be listed explicitly - if(in_array($this->owner->class, $blacklisted)) return false; - } - } +class SiteTreeSubsites extends DataExtension +{ + + private static $has_one = [ + 'Subsite' => Subsite::class, // The subsite that this page belongs to + ]; + + private static $many_many = [ + 'CrossSubsiteLinkTracking' => 'SilverStripe\\CMS\\Model\\SiteTree' + // Stored separately, as the logic for URL rewriting is different + ]; + + private static $many_many_extraFields = [ + "CrossSubsiteLinkTracking" => ["FieldName" => "Varchar"] + ]; + + function isMainSite() + { + if ($this->owner->SubsiteID == 0) { + return true; + } + return false; + } + + /** + * Update any requests to limit the results to the current site + */ + public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) + { + if (Subsite::$disable_subsite_filter) { + return; + } + if ($dataQuery->getQueryParam('Subsite.filter') === false) { + return; + } + + // If you're querying by ID, ignore the sub-site - this is a bit ugly... + // if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) { + if ($query->filtersOnID()) { + return; + } + + if (Subsite::$force_subsite) { + $subsiteID = Subsite::$force_subsite; + } else { + /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; + else */ + $subsiteID = (int)Subsite::currentSubsiteID(); + } + + // The foreach is an ugly way of getting the first key :-) + foreach ($query->getFrom() as $tableName => $info) { + // The tableName should be SiteTree or SiteTree_Live... + if (strpos($tableName, SiteTree::class) === false) { + break; + } + $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)"); + break; + } + } + + function onBeforeWrite() + { + if (!$this->owner->ID && !$this->owner->SubsiteID) { + $this->owner->SubsiteID = Subsite::currentSubsiteID(); + } + + parent::onBeforeWrite(); + } + + function updateCMSFields(FieldList $fields) + { + $subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain"); + $subsitesMap = []; + if ($subsites && $subsites->Count()) { + $subsitesMap = $subsites->map('ID', 'Title')->toArray(); + unset($subsitesMap[$this->owner->SubsiteID]); + } + + // Master page edit field (only allowed from default subsite to avoid inconsistent relationships) + $isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite; + if ($isDefaultSubsite && $subsitesMap) { + $fields->addFieldToTab( + 'Root.Main', + new DropdownField( + "CopyToSubsiteID", + _t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"), + $subsitesMap, + '' + ) + ); + $fields->addFieldToTab( + 'Root.Main', + $copyAction = new InlineFormAction( + "copytosubsite", + _t('SiteTreeSubsites.CopyAction', "Copy") + ) + ); + } + + // replace readonly link prefix + $subsite = $this->owner->Subsite(); + $nested_urls_enabled = Config::inst()->get('SilverStripe\\CMS\\Model\\SiteTree', 'nested_urls'); + if ($subsite && $subsite->ID) { + $baseUrl = Director::protocol() . $subsite->domain() . '/'; + $baseLink = Controller::join_links( + $baseUrl, + ($nested_urls_enabled && $this->owner->ParentID ? $this->owner->Parent()->RelativeLink(true) : null) + ); + + $urlsegment = $fields->dataFieldByName('URLSegment'); + $urlsegment->setURLPrefix($baseLink); + } + } + + function alternateSiteConfig() + { + if (!$this->owner->SubsiteID) { + return false; + } + $sc = DataObject::get_one('SilverStripe\\SiteConfig\\SiteConfig', '"SubsiteID" = ' . $this->owner->SubsiteID); + if (!$sc) { + $sc = new SiteConfig(); + $sc->SubsiteID = $this->owner->SubsiteID; + $sc->Title = _t('Subsite.SiteConfigTitle', 'Your Site Name'); + $sc->Tagline = _t('Subsite.SiteConfigSubtitle', 'Your tagline here'); + $sc->write(); + } + return $sc; + } + + /** + * Only allow editing of a page if the member satisfies one of the following conditions: + * - Is in a group which has access to the subsite this page belongs to + * - Is in a group with edit permissions on the "main site" + * + * @return boolean + */ + function canEdit($member = null) + { + + if (!$member) { + $member = Member::currentUser(); + } + + // Find the sites that this user has access to + $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true, 'all', $member)->column('ID'); + + if (!is_null($this->owner->SubsiteID)) { + $subsiteID = $this->owner->SubsiteID; + } else { + // The relationships might not be available during the record creation when using a GridField. + // In this case the related objects will have empty fields, and SubsiteID will not be available. + // + // We do the second best: fetch the likely SubsiteID from the session. The drawback is this might + // make it possible to force relations to point to other (forbidden) subsites. + $subsiteID = Subsite::currentSubsiteID(); + } + + // Return true if they have access to this object's site + if (!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) { + return false; + } + } + + /** + * @return boolean + */ + function canDelete($member = null) + { + if (!$member && $member !== FALSE) { + $member = Member::currentUser(); + } + + return $this->canEdit($member); + } + + /** + * @return boolean + */ + function canAddChildren($member = null) + { + if (!$member && $member !== FALSE) { + $member = Member::currentUser(); + } + + return $this->canEdit($member); + } + + /** + * @return boolean + */ + function canPublish($member = null) + { + if (!$member && $member !== FALSE) { + $member = Member::currentUser(); + } + + return $this->canEdit($member); + } + + /** + * Does the basic duplication, but doesn't write anything + * this means we can subclass this easier and do more complex + * relation duplication. + */ + public function duplicateToSubsitePrep($subsiteID) + { + if (is_object($subsiteID)) { + $subsiteID = $subsiteID->ID; + } + + $oldSubsite = Subsite::currentSubsiteID(); + if ($subsiteID) { + Subsite::changeSubsite($subsiteID); + } else { + $subsiteID = $oldSubsite; + } + // doesn't write as we need to reset the SubsiteID, ParentID etc + $clone = $this->owner->duplicate(false); + $clone->CheckedPublicationDifferences = $clone->AddedToStage = true; + $subsiteID = ($subsiteID ? $subsiteID : $oldSubsite); + $clone->SubsiteID = $subsiteID; + // We have no idea what the parentID should be, so as a workaround use the url-segment and subsite ID + if ($this->owner->Parent()) { + $parentSeg = $this->owner->Parent()->URLSegment; + $newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first(); + if ($newParentPage) { + $clone->ParentID = $newParentPage->ID; + } else { + // reset it to the top level, so the user can decide where to put it + $clone->ParentID = 0; + } + } + // MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module + $clone->MasterPageID = $this->owner->ID; + return $clone; + } + + /** + * Create a duplicate of this page and save it to another subsite + * @param $subsiteID int|Subsite The Subsite to copy to, or its ID + */ + public function duplicateToSubsite($subsiteID = null) + { + $clone = $this->owner->duplicateToSubsitePrep($subsiteID); + $clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner); + $clone->write(); + $clone->duplicateSubsiteRelations($this->owner); + // new extension hooks which happens after write, + // onAfterDuplicate isn't reliable due to + // https://github.com/silverstripe/silverstripe-cms/issues/1253 + $clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner); + return $clone; + } + + /** + * Duplicate relations using a static property to define + * which ones we want to duplicate + * + * It may be that some relations are not diostinct to sub site so can stay + * whereas others may need to be duplicated + * + */ + public function duplicateSubsiteRelations($originalPage) + { + $thisClass = $originalPage->ClassName; + $relations = Config::inst()->get($thisClass, 'duplicate_to_subsite_relations'); + + if ($relations && !empty($relations)) { + foreach ($relations as $relation) { + $items = $originalPage->$relation(); + foreach ($items as $item) { + $duplicateItem = $item->duplicate(false); + $duplicateItem->{$thisClass . 'ID'} = $this->owner->ID; + $duplicateItem->write(); + } + } + } + } + + /** + * Called by ContentController::init(); + */ + static function contentcontrollerInit($controller) + { + $subsite = Subsite::currentSubsite(); + + if ($subsite && $subsite->Theme) { + Config::modify()->set('SilverStripe\\View\\SSViewer', 'theme', Subsite::currentSubsite()->Theme); + } + } + + function alternateAbsoluteLink() + { + // Generate the existing absolute URL and replace the domain with the subsite domain. + // This helps deal with Link() returning an absolute URL. + $url = Director::absoluteURL($this->owner->Link()); + if ($this->owner->SubsiteID) { + $url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url); + } + return $url; + } + + /** + * Use the CMS domain for iframed CMS previews to prevent single-origin violations + * and SSL cert problems. + */ + function alternatePreviewLink($action = null) + { + $url = Director::absoluteURL($this->owner->Link()); + if ($this->owner->SubsiteID) { + $url = HTTP::setGetVar('SubsiteID', $this->owner->SubsiteID, $url); + } + return $url; + } + + /** + * Inject the subsite ID into the content so it can be used by frontend scripts. + */ + function MetaTags(&$tags) + { + if ($this->owner->SubsiteID) { + $tags .= "owner->SubsiteID . "\" />\n"; + } + + return $tags; + } + + function augmentSyncLinkTracking() + { + // Set LinkTracking appropriately + $links = HTTP::getLinksIn($this->owner->Content); + $linkedPages = []; + + if ($links) { + foreach ($links as $link) { + if (substr($link, 0, strlen('http://')) == 'http://') { + $withoutHttp = substr($link, strlen('http://')); + if (strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) { + $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/')); + $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1); + + $subsiteID = Subsite::getSubsiteIDForDomain($domain); + if ($subsiteID == 0) { + continue; + } // We have no idea what the domain for the main site is, so cant track links to it + + $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; + Subsite::disable_subsite_filter(true); + $candidatePage = DataObject::get_one("SilverStripe\\CMS\\Model\\SiteTree", + "\"URLSegment\" = '" . Convert::raw2sql(urldecode($rest)) . "' AND \"SubsiteID\" = " . $subsiteID, + false); + Subsite::disable_subsite_filter($origDisableSubsiteFilter); + + if ($candidatePage) { + $linkedPages[] = $candidatePage->ID; + } else { + $this->owner->HasBrokenLink = true; + } + } + } + } + } + + $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages); + } + + /** + * Return a piece of text to keep DataObject cache keys appropriately specific + */ + function cacheKeyComponent() + { + return 'subsite-' . Subsite::currentSubsiteID(); + } + + /** + * @param Member + * @return boolean|null + */ + function canCreate($member = null) + { + // Typically called on a singleton, so we're not using the Subsite() relation + $subsite = Subsite::currentSubsite(); + if ($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) { + $blacklisted = explode(',', $subsite->PageTypeBlacklist); + // All subclasses need to be listed explicitly + if (in_array($this->owner->class, $blacklisted)) { + return false; + } + } + } } diff --git a/code/extensions/SubsiteMenuExtension.php b/code/extensions/SubsiteMenuExtension.php index 9dc4f479..669a0b0c 100644 --- a/code/extensions/SubsiteMenuExtension.php +++ b/code/extensions/SubsiteMenuExtension.php @@ -6,10 +6,9 @@ use SilverStripe\Core\Extension; - /* * Simple extension to show admins in the menu of subsites. - * If an admin area should be available to a subsite, you can attach + * If an admin area should be available to a subsite, you can attach * this class to your admin in config. eg: * * MyAdmin::add_extension('SubsiteMenuExtension'); @@ -17,10 +16,12 @@ * Or you can include the subsiteCMSShowInMenu function in your admin class and have it return true */ -class SubsiteMenuExtension extends Extension{ - - public function subsiteCMSShowInMenu(){ - return true; - } +class SubsiteMenuExtension extends Extension +{ + + public function subsiteCMSShowInMenu() + { + return true; + } } diff --git a/code/forms/GridFieldSubsiteDetailForm.php b/code/forms/GridFieldSubsiteDetailForm.php index 6b0d846d..946204c7 100644 --- a/code/forms/GridFieldSubsiteDetailForm.php +++ b/code/forms/GridFieldSubsiteDetailForm.php @@ -3,61 +3,65 @@ namespace SilverStripe\Subsites\Forms; -use SilverStripe\Forms\GridField\GridFieldDetailForm; use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\GridField\GridFieldDetailForm; use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest; - use SilverStripe\Subsites\Model\Subsite; -class GridFieldSubsiteDetailForm extends GridFieldDetailForm { - protected $itemRequestClass=GridFieldSubsiteDetailForm_ItemRequest::class; +class GridFieldSubsiteDetailForm extends GridFieldDetailForm +{ + protected $itemRequestClass = GridFieldSubsiteDetailForm_ItemRequest::class; } -class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest { - - private static $allowed_actions = array( - 'ItemEditForm', - ); - - /** - * Builds an item edit form. The arguments to getCMSFields() are the popupController and - * popupFormName, however this is an experimental API and may change. - * - * @todo In the future, we will probably need to come up with a tigher object representing a partially - * complete controller with gaps for extra functionality. This, for example, would be a better way - * of letting Security/login put its log-in form inside a UI specified elsewhere. - * - * @return Form - * @see GridFieldDetailForm_ItemRequest::ItemEditForm() - */ - function ItemEditForm() { - $form=parent::ItemEditForm(); - - if($this->record->ID == 0) { - $templates = Subsite::get()->sort('Title'); - $templateArray = array(); - if($templates) { - $templateArray = $templates->map('ID', 'Title'); - } - - $templateDropdown = new DropdownField('TemplateID', _t('Subsite.COPYSTRUCTURE', 'Copy structure from:'), $templateArray); - $templateDropdown->setEmptyString('(' . _t('Subsite.NOTEMPLATE', 'No template') . ')'); - $form->Fields()->addFieldToTab('Root.Configuration', $templateDropdown); - } - - return $form; - } - - function doSave($data, $form) { - $new_record = $this->record->ID == 0; - if($new_record && isset($data['TemplateID']) && !empty($data['TemplateID'])) { - $template = Subsite::get()->byID(intval($data['TemplateID'])); - if($template) { - $this->record = $template->duplicate(); - } - } - - return parent::doSave($data, $form); - } +class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest +{ + + private static $allowed_actions = [ + 'ItemEditForm', + ]; + + /** + * Builds an item edit form. The arguments to getCMSFields() are the popupController and + * popupFormName, however this is an experimental API and may change. + * + * @todo In the future, we will probably need to come up with a tigher object representing a partially + * complete controller with gaps for extra functionality. This, for example, would be a better way + * of letting Security/login put its log-in form inside a UI specified elsewhere. + * + * @return Form + * @see GridFieldDetailForm_ItemRequest::ItemEditForm() + */ + function ItemEditForm() + { + $form = parent::ItemEditForm(); + + if ($this->record->ID == 0) { + $templates = Subsite::get()->sort('Title'); + $templateArray = []; + if ($templates) { + $templateArray = $templates->map('ID', 'Title'); + } + + $templateDropdown = new DropdownField('TemplateID', _t('Subsite.COPYSTRUCTURE', 'Copy structure from:'), + $templateArray); + $templateDropdown->setEmptyString('(' . _t('Subsite.NOTEMPLATE', 'No template') . ')'); + $form->Fields()->addFieldToTab('Root.Configuration', $templateDropdown); + } + + return $form; + } + + function doSave($data, $form) + { + $new_record = $this->record->ID == 0; + if ($new_record && isset($data['TemplateID']) && !empty($data['TemplateID'])) { + $template = Subsite::get()->byID(intval($data['TemplateID'])); + if ($template) { + $this->record = $template->duplicate(); + } + } + + return parent::doSave($data, $form); + } } diff --git a/code/forms/SubsitesTreeDropdownField.php b/code/forms/SubsitesTreeDropdownField.php index 895ff1ba..7b190d9b 100644 --- a/code/forms/SubsitesTreeDropdownField.php +++ b/code/forms/SubsitesTreeDropdownField.php @@ -3,11 +3,10 @@ namespace SilverStripe\Subsites\Forms; -use SilverStripe\View\Requirements; use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\Session; use SilverStripe\Forms\TreeDropdownField; - +use SilverStripe\View\Requirements; /** @@ -16,40 +15,45 @@ * * @package subsites */ -class SubsitesTreeDropdownField extends TreeDropdownField { +class SubsitesTreeDropdownField extends TreeDropdownField +{ - private static $allowed_actions = array( - 'tree' - ); + private static $allowed_actions = [ + 'tree' + ]; - protected $subsiteID = 0; + protected $subsiteID = 0; - protected $extraClasses = array(SubsitesTreeDropdownField::class); + protected $extraClasses = [SubsitesTreeDropdownField::class]; - function Field($properties = array()) { - $html = parent::Field($properties); + function Field($properties = []) + { + $html = parent::Field($properties); - Requirements::javascript('subsites/javascript/SubsitesTreeDropdownField.js'); + Requirements::javascript('subsites/javascript/SubsitesTreeDropdownField.js'); - return $html; - } + return $html; + } - function setSubsiteID($id) { - $this->subsiteID = $id; - } + function setSubsiteID($id) + { + $this->subsiteID = $id; + } - function getSubsiteID() { - return $this->subsiteID; - } + function getSubsiteID() + { + return $this->subsiteID; + } - function tree(HTTPRequest $request) { - $oldSubsiteID = Session::get('SubsiteID'); - Session::set('SubsiteID', $this->subsiteID); + function tree(HTTPRequest $request) + { + $oldSubsiteID = Session::get('SubsiteID'); + Session::set('SubsiteID', $this->subsiteID); - $results = parent::tree($request); + $results = parent::tree($request); - Session::set('SubsiteID', $oldSubsiteID); + Session::set('SubsiteID', $oldSubsiteID); - return $results; - } + return $results; + } } diff --git a/code/model/Subsite.php b/code/model/Subsite.php index 23675400..d570e872 100644 --- a/code/model/Subsite.php +++ b/code/model/Subsite.php @@ -3,37 +3,34 @@ namespace SilverStripe\Subsites\Model; -use SilverStripe\i18n\Data\Intl\IntlLocales; -use SilverStripe\ORM\DataObject; -use SilverStripe\Control\Session; -use SilverStripe\i18n\i18n; -use SilverStripe\Security\Permission; -use SilverStripe\Security\Member; -use SilverStripe\Core\Convert; -use SilverStripe\ORM\ArrayList; use SilverStripe\Admin\CMSMenu; -use SilverStripe\ORM\DataList; -use SilverStripe\Control\Director; -use SilverStripe\ORM\DB; -use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; -use SilverStripe\Forms\GridField\GridField; -use SilverStripe\Forms\LiteralField; -use SilverStripe\Forms\DropdownField; use SilverStripe\CMS\Model\SiteTree; -use SilverStripe\Forms\HeaderField; -use SilverStripe\Forms\TextField; +use SilverStripe\Control\Director; +use SilverStripe\Control\Session; +use SilverStripe\Core\Convert; use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\CheckboxSetField; +use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\GridField\GridField; +use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; +use SilverStripe\Forms\HeaderField; +use SilverStripe\Forms\HiddenField; +use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\Tab; use SilverStripe\Forms\TabSet; -use SilverStripe\Forms\HiddenField; -use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\TextField; +use SilverStripe\i18n\Data\Intl\IntlLocales; +use SilverStripe\i18n\i18n; use SilverStripe\ORM\ArrayLib; +use SilverStripe\ORM\ArrayList; +use SilverStripe\ORM\DataList; +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\DB; +use SilverStripe\Security\Member; +use SilverStripe\Security\Permission; use SilverStripe\Versioned\Versioned; use UnexpectedValueException; -use SilverStripe\Security\Group; -use SilverStripe\Security\PermissionRole; -use SilverStripe\Security\PermissionRoleCode; /** @@ -42,432 +39,513 @@ * * @package subsites */ -class Subsite extends DataObject { +class Subsite extends DataObject +{ private static $table_name = 'Subsite'; - /** - * @var $use_session_subsiteid Boolean Set to TRUE when using the CMS and FALSE - * when browsing the frontend of a website. - * - * @todo Remove flag once the Subsite CMS works without session state, - * similarly to the Translatable module. - */ - public static $use_session_subsiteid = false; - - /** - * @var boolean $disable_subsite_filter If enabled, bypasses the query decoration - * to limit DataObject::get*() calls to a specific subsite. Useful for debugging. - */ - public static $disable_subsite_filter = false; - - /** - * Allows you to force a specific subsite ID, or comma separated list of IDs. - * Only works for reading. An object cannot be written to more than 1 subsite. - */ - public static $force_subsite = null; - - /** - * - * @var boolean - */ - public static $write_hostmap = true; - - /** - * Memory cache of accessible sites - * - * @array - */ - private static $_cache_accessible_sites = array(); - - /** - * Memory cache of subsite id for domains - * - * @var array - */ - private static $_cache_subsite_for_domain = array(); - - /** - * @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites. - * Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder - * are listed. - */ - private static $allowed_themes = array(); - - /** - * @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same. - * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com') - * in both TRUE or FALSE setting. - */ - public static $strict_subdomain_matching = false; - - /** - * @var boolean Respects the IsPublic flag when retrieving subsites - */ - public static $check_is_public = true; - - /** - * @return array - */ - private static $summary_fields = array( - 'Title', - 'PrimaryDomain', - 'IsPublic' - ); - - /** - * Set allowed themes - * - * @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites. - */ - public static function set_allowed_themes($themes) { - self::$allowed_themes = $themes; - } - - /** - * Gets the subsite currently set in the session. - * - * @uses ControllerSubsites->controllerAugmentInit() - * @return Subsite - */ - public static function currentSubsite() { - // get_by_id handles caching so we don't have to - return DataObject::get_by_id(Subsite::class, self::currentSubsiteID()); - } - - /** - * This function gets the current subsite ID from the session. It used in the backend so Ajax requests - * use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain - * directly from ModelAsController::getNestedController. Only gets Subsite instances which have their - * {@link IsPublic} flag set to TRUE. - * - * You can simulate subsite access without creating virtual hosts by appending ?SubsiteID= to the request. - * - * @todo Pass $request object from controller so we don't have to rely on $_GET - * - * @param boolean $cache - * @return int ID of the current subsite instance - */ - public static function currentSubsiteID() { - $id = NULL; - - if(isset($_GET['SubsiteID'])) { - $id = (int)$_GET['SubsiteID']; - } else if (Subsite::$use_session_subsiteid) { - $id = Session::get('SubsiteID'); - } - - if($id === NULL) { - $id = self::getSubsiteIDForDomain(); - } - - return (int)$id; - } - - /** - * Switch to another subsite through storing the subsite identifier in the current PHP session. - * Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE. - * - * @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself - */ - public static function changeSubsite($subsite) { - // Session subsite change only meaningful if the session is active. - // Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID. - if (!Subsite::$use_session_subsiteid) return; - - if(is_object($subsite)) $subsiteID = $subsite->ID; - else $subsiteID = $subsite; - - Session::set('SubsiteID', (int)$subsiteID); - - // Set locale - if (is_object($subsite) && $subsite->Language != '') { - $locale = i18n::get_locale_from_lang($subsite->Language); - if($locale) { - i18n::set_locale($locale); - } - } - - Permission::flush_permission_cache(); - } - - /** - * Get a matching subsite for the given host, or for the current HTTP_HOST. - * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string, - * for example matching all subdomains on *.example.com with one subsite, - * and all subdomains on *.example.org on another. - * - * @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. - * @return int Subsite ID - */ - public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) { - if($host == null && isset($_SERVER['HTTP_HOST'])) { - $host = $_SERVER['HTTP_HOST']; - } - - $matchingDomains = null; - $cacheKey = null; - if ($host) { - if(!self::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host); - - $cacheKey = implode('_', array($host, Member::currentUserID(), self::$check_is_public)); - if(isset(self::$_cache_subsite_for_domain[$cacheKey])) return self::$_cache_subsite_for_domain[$cacheKey]; - - $SQL_host = Convert::raw2sql($host); - $matchingDomains = DataObject::get( - SubsiteDomain::class, - "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')", - "\"IsPrimary\" DESC" - )->innerJoin('Subsite', "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1"); - } - - if($matchingDomains && $matchingDomains->Count()) { - $subsiteIDs = array_unique($matchingDomains->column('SubsiteID')); - $subsiteDomains = array_unique($matchingDomains->column('Domain')); - if(sizeof($subsiteIDs) > 1) { - throw new UnexpectedValueException(sprintf( - "Multiple subsites match on '%s': %s", - $host, - implode(',', $subsiteDomains) - )); - } - - $subsiteID = $subsiteIDs[0]; - } else if($default = DataObject::get_one(Subsite::class, "\"DefaultSite\" = 1")) { - // Check for a 'default' subsite - $subsiteID = $default->ID; - } else { - // Default subsite id = 0, the main site - $subsiteID = 0; - } - - if ($cacheKey) { - self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID; - } - - return $subsiteID; - } - - /** - * - * @param string $className - * @param string $filter - * @param string $sort - * @param string $join - * @param string $limit - * @return DataList - */ - public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") { - $result = DataObject::get($className, $filter, $sort, $join, $limit); - $result = $result->setDataQueryParam('Subsite.filter', false); - return $result; - } - - /** - * Disable the sub-site filtering; queries will select from all subsites - */ - public static function disable_subsite_filter($disabled = true) { - self::$disable_subsite_filter = $disabled; - } - - /** - * Flush caches on database reset - */ - public static function on_db_reset() { - self::$_cache_accessible_sites = array(); - self::$_cache_subsite_for_domain = array(); - } - - /** - * Return all subsites, regardless of permissions (augmented with main site). - * - * @return SS_List List of {@link Subsite} objects (DataList or ArrayList). - */ - public static function all_sites($includeMainSite = true, $mainSiteTitle = "Main site") { - $subsites = Subsite::get(); - - if($includeMainSite) { - $subsites = $subsites->toArray(); - - $mainSite = new Subsite(); - $mainSite->Title = $mainSiteTitle; - array_unshift($subsites, $mainSite); - - $subsites = ArrayList::create($subsites); - } - - return $subsites; - } - - /* - * Returns an ArrayList of the subsites accessible to the current user. - * It's enough for any section to be accessible for the site to be included. - * - * @return ArrayList of {@link Subsite} instances. - */ - public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) { - // Rationalise member arguments - if(!$member) $member = Member::currentUser(); - if(!$member) return new ArrayList(); - if(!is_object($member)) $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); - - $subsites = new ArrayList(); - - // Collect subsites for all sections. - $menu = CMSMenu::get_viewable_menu_items(); - foreach($menu as $candidate) { - if ($candidate->controller) { - $accessibleSites = singleton($candidate->controller)->sectionSites( - $includeMainSite, - $mainSiteTitle, - $member - ); - - // Replace existing keys so no one site appears twice. - $subsites->merge($accessibleSites); - } - } - - $subsites->removeDuplicates(); - - return $subsites; - } - - /** - * Return the subsites that the current user can access by given permission. - * Sites will only be included if they have a Title. - * - * @param $permCode array|string Either a single permission code or an array of permission codes. - * @param $includeMainSite If true, the main site will be included if appropriate. - * @param $mainSiteTitle The label to give to the main site - * @param $member - * @return DataList of {@link Subsite} instances - */ - public static function accessible_sites($permCode, $includeMainSite = true, $mainSiteTitle = "Main site", $member = null) { - // Rationalise member arguments - if(!$member) $member = Member::currentUser(); - if(!$member) return new ArrayList(); - if(!is_object($member)) $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); - - // Rationalise permCode argument - if(is_array($permCode)) $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'"; - else $SQL_codes = "'" . Convert::raw2sql($permCode) . "'"; - - // Cache handling - $cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle; - if(isset(self::$_cache_accessible_sites[$cacheKey])) { - return self::$_cache_accessible_sites[$cacheKey]; - } - - $subsites = DataList::create(Subsite::class) - ->where("\"Subsite\".\"Title\" != ''") - ->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"") - ->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1") - ->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID") - ->innerJoin('Permission', "\"Group\".\"ID\"=\"Permission\".\"GroupID\" AND \"Permission\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"); - - if(!$subsites) $subsites = new ArrayList(); - - $rolesSubsites = DataList::create(Subsite::class) - ->where("\"Subsite\".\"Title\" != ''") - ->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"") - ->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1") - ->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID") - ->innerJoin('Group_Roles', "\"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"") - ->innerJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"") - ->innerJoin('PermissionRoleCode', "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" AND \"PermissionRoleCode\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"); - - if(!$subsites && $rolesSubsites) return $rolesSubsites; - - $subsites = new ArrayList($subsites->toArray()); - - if($rolesSubsites) foreach($rolesSubsites as $subsite) { - if(!$subsites->find('ID', $subsite->ID)) { - $subsites->push($subsite); - } - } - - if($includeMainSite) { - if(!is_array($permCode)) $permCode = array($permCode); - if(self::hasMainSitePermission($member, $permCode)) { - $subsites=$subsites->toArray(); - - $mainSite = new Subsite(); - $mainSite->Title = $mainSiteTitle; - array_unshift($subsites, $mainSite); - $subsites=ArrayList::create($subsites); - } - } - - self::$_cache_accessible_sites[$cacheKey] = $subsites; - - return $subsites; - } - - /** - * Write a host->domain map to subsites/host-map.php - * - * This is used primarily when using subsites in conjunction with StaticPublisher - * - * @param string $file - filepath of the host map to be written - * @return void - */ - public static function writeHostMap($file = null) { - if (!self::$write_hostmap) return; - - if (!$file) $file = Director::baseFolder().'/subsites/host-map.php'; - $hostmap = array(); - - $subsites = DataObject::get(Subsite::class); - - if ($subsites) foreach($subsites as $subsite) { - $domains = $subsite->Domains(); - if ($domains) foreach($domains as $domain) { - $domainStr = $domain->Domain; - if(!self::$strict_subdomain_matching) $domainStr = preg_replace('/^www\./', '', $domainStr); - $hostmap[$domainStr] = $subsite->domain(); - } - if ($subsite->DefaultSite) $hostmap['default'] = $subsite->domain(); - } - - $data = "ID; - - // Count this user's groups which can access the main site - $groupCount = DB::query(" + /** + * @var $use_session_subsiteid Boolean Set to TRUE when using the CMS and FALSE + * when browsing the frontend of a website. + * + * @todo Remove flag once the Subsite CMS works without session state, + * similarly to the Translatable module. + */ + public static $use_session_subsiteid = false; + + /** + * @var boolean $disable_subsite_filter If enabled, bypasses the query decoration + * to limit DataObject::get*() calls to a specific subsite. Useful for debugging. + */ + public static $disable_subsite_filter = false; + + /** + * Allows you to force a specific subsite ID, or comma separated list of IDs. + * Only works for reading. An object cannot be written to more than 1 subsite. + */ + public static $force_subsite = null; + + /** + * + * @var boolean + */ + public static $write_hostmap = true; + + /** + * Memory cache of accessible sites + * + * @array + */ + private static $_cache_accessible_sites = []; + + /** + * Memory cache of subsite id for domains + * + * @var array + */ + private static $_cache_subsite_for_domain = []; + + /** + * @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites. + * Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder + * are listed. + */ + private static $allowed_themes = []; + + /** + * @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same. + * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com') + * in both TRUE or FALSE setting. + */ + public static $strict_subdomain_matching = false; + + /** + * @var boolean Respects the IsPublic flag when retrieving subsites + */ + public static $check_is_public = true; + + /** + * @return array + */ + private static $summary_fields = [ + 'Title', + 'PrimaryDomain', + 'IsPublic' + ]; + + /** + * Set allowed themes + * + * @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites. + */ + public static function set_allowed_themes($themes) + { + self::$allowed_themes = $themes; + } + + /** + * Gets the subsite currently set in the session. + * + * @uses ControllerSubsites->controllerAugmentInit() + * @return Subsite + */ + public static function currentSubsite() + { + // get_by_id handles caching so we don't have to + return DataObject::get_by_id(Subsite::class, self::currentSubsiteID()); + } + + /** + * This function gets the current subsite ID from the session. It used in the backend so Ajax requests + * use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain + * directly from ModelAsController::getNestedController. Only gets Subsite instances which have their + * {@link IsPublic} flag set to TRUE. + * + * You can simulate subsite access without creating virtual hosts by appending ?SubsiteID= to the request. + * + * @todo Pass $request object from controller so we don't have to rely on $_GET + * + * @param boolean $cache + * @return int ID of the current subsite instance + */ + public static function currentSubsiteID() + { + $id = NULL; + + if (isset($_GET['SubsiteID'])) { + $id = (int)$_GET['SubsiteID']; + } else { + if (Subsite::$use_session_subsiteid) { + $id = Session::get('SubsiteID'); + } + } + + if ($id === NULL) { + $id = self::getSubsiteIDForDomain(); + } + + return (int)$id; + } + + /** + * Switch to another subsite through storing the subsite identifier in the current PHP session. + * Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE. + * + * @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself + */ + public static function changeSubsite($subsite) + { + // Session subsite change only meaningful if the session is active. + // Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID. + if (!Subsite::$use_session_subsiteid) { + return; + } + + if (is_object($subsite)) { + $subsiteID = $subsite->ID; + } else { + $subsiteID = $subsite; + } + + Session::set('SubsiteID', (int)$subsiteID); + + // Set locale + if (is_object($subsite) && $subsite->Language != '') { + $locale = i18n::get_locale_from_lang($subsite->Language); + if ($locale) { + i18n::set_locale($locale); + } + } + + Permission::flush_permission_cache(); + } + + /** + * Get a matching subsite for the given host, or for the current HTTP_HOST. + * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string, + * for example matching all subdomains on *.example.com with one subsite, + * and all subdomains on *.example.org on another. + * + * @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. + * @return int Subsite ID + */ + public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) + { + if ($host == null && isset($_SERVER['HTTP_HOST'])) { + $host = $_SERVER['HTTP_HOST']; + } + + $matchingDomains = null; + $cacheKey = null; + if ($host) { + if (!self::$strict_subdomain_matching) { + $host = preg_replace('/^www\./', '', $host); + } + + $cacheKey = implode('_', [$host, Member::currentUserID(), self::$check_is_public]); + if (isset(self::$_cache_subsite_for_domain[$cacheKey])) { + return self::$_cache_subsite_for_domain[$cacheKey]; + } + + $SQL_host = Convert::raw2sql($host); + $matchingDomains = DataObject::get( + SubsiteDomain::class, + "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')", + "\"IsPrimary\" DESC" + )->innerJoin('Subsite', + "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1"); + } + + if ($matchingDomains && $matchingDomains->Count()) { + $subsiteIDs = array_unique($matchingDomains->column('SubsiteID')); + $subsiteDomains = array_unique($matchingDomains->column('Domain')); + if (sizeof($subsiteIDs) > 1) { + throw new UnexpectedValueException(sprintf( + "Multiple subsites match on '%s': %s", + $host, + implode(',', $subsiteDomains) + )); + } + + $subsiteID = $subsiteIDs[0]; + } else { + if ($default = DataObject::get_one(Subsite::class, "\"DefaultSite\" = 1")) { + // Check for a 'default' subsite + $subsiteID = $default->ID; + } else { + // Default subsite id = 0, the main site + $subsiteID = 0; + } + } + + if ($cacheKey) { + self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID; + } + + return $subsiteID; + } + + /** + * + * @param string $className + * @param string $filter + * @param string $sort + * @param string $join + * @param string $limit + * @return DataList + */ + public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") + { + $result = DataObject::get($className, $filter, $sort, $join, $limit); + $result = $result->setDataQueryParam('Subsite.filter', false); + return $result; + } + + /** + * Disable the sub-site filtering; queries will select from all subsites + */ + public static function disable_subsite_filter($disabled = true) + { + self::$disable_subsite_filter = $disabled; + } + + /** + * Flush caches on database reset + */ + public static function on_db_reset() + { + self::$_cache_accessible_sites = []; + self::$_cache_subsite_for_domain = []; + } + + /** + * Return all subsites, regardless of permissions (augmented with main site). + * + * @return SS_List List of {@link Subsite} objects (DataList or ArrayList). + */ + public static function all_sites($includeMainSite = true, $mainSiteTitle = "Main site") + { + $subsites = Subsite::get(); + + if ($includeMainSite) { + $subsites = $subsites->toArray(); + + $mainSite = new Subsite(); + $mainSite->Title = $mainSiteTitle; + array_unshift($subsites, $mainSite); + + $subsites = ArrayList::create($subsites); + } + + return $subsites; + } + + /* + * Returns an ArrayList of the subsites accessible to the current user. + * It's enough for any section to be accessible for the site to be included. + * + * @return ArrayList of {@link Subsite} instances. + */ + public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) + { + // Rationalise member arguments + if (!$member) { + $member = Member::currentUser(); + } + if (!$member) { + return new ArrayList(); + } + if (!is_object($member)) { + $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); + } + + $subsites = new ArrayList(); + + // Collect subsites for all sections. + $menu = CMSMenu::get_viewable_menu_items(); + foreach ($menu as $candidate) { + if ($candidate->controller) { + $accessibleSites = singleton($candidate->controller)->sectionSites( + $includeMainSite, + $mainSiteTitle, + $member + ); + + // Replace existing keys so no one site appears twice. + $subsites->merge($accessibleSites); + } + } + + $subsites->removeDuplicates(); + + return $subsites; + } + + /** + * Return the subsites that the current user can access by given permission. + * Sites will only be included if they have a Title. + * + * @param $permCode array|string Either a single permission code or an array of permission codes. + * @param $includeMainSite If true, the main site will be included if appropriate. + * @param $mainSiteTitle The label to give to the main site + * @param $member + * @return DataList of {@link Subsite} instances + */ + public static function accessible_sites( + $permCode, + $includeMainSite = true, + $mainSiteTitle = "Main site", + $member = null + ) + { + // Rationalise member arguments + if (!$member) { + $member = Member::currentUser(); + } + if (!$member) { + return new ArrayList(); + } + if (!is_object($member)) { + $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); + } + + // Rationalise permCode argument + if (is_array($permCode)) { + $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'"; + } else { + $SQL_codes = "'" . Convert::raw2sql($permCode) . "'"; + } + + // Cache handling + $cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle; + if (isset(self::$_cache_accessible_sites[$cacheKey])) { + return self::$_cache_accessible_sites[$cacheKey]; + } + + $subsites = DataList::create(Subsite::class) + ->where("\"Subsite\".\"Title\" != ''") + ->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"") + ->innerJoin('Group', + "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1") + ->innerJoin('Group_Members', + "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID") + ->innerJoin('Permission', + "\"Group\".\"ID\"=\"Permission\".\"GroupID\" AND \"Permission\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"); + + if (!$subsites) { + $subsites = new ArrayList(); + } + + $rolesSubsites = DataList::create(Subsite::class) + ->where("\"Subsite\".\"Title\" != ''") + ->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"") + ->innerJoin('Group', + "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1") + ->innerJoin('Group_Members', + "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID") + ->innerJoin('Group_Roles', "\"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"") + ->innerJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"") + ->innerJoin('PermissionRoleCode', + "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" AND \"PermissionRoleCode\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"); + + if (!$subsites && $rolesSubsites) { + return $rolesSubsites; + } + + $subsites = new ArrayList($subsites->toArray()); + + if ($rolesSubsites) { + foreach ($rolesSubsites as $subsite) { + if (!$subsites->find('ID', $subsite->ID)) { + $subsites->push($subsite); + } + } + } + + if ($includeMainSite) { + if (!is_array($permCode)) { + $permCode = [$permCode]; + } + if (self::hasMainSitePermission($member, $permCode)) { + $subsites = $subsites->toArray(); + + $mainSite = new Subsite(); + $mainSite->Title = $mainSiteTitle; + array_unshift($subsites, $mainSite); + $subsites = ArrayList::create($subsites); + } + } + + self::$_cache_accessible_sites[$cacheKey] = $subsites; + + return $subsites; + } + + /** + * Write a host->domain map to subsites/host-map.php + * + * This is used primarily when using subsites in conjunction with StaticPublisher + * + * @param string $file - filepath of the host map to be written + * @return void + */ + public static function writeHostMap($file = null) + { + if (!self::$write_hostmap) { + return; + } + + if (!$file) { + $file = Director::baseFolder() . '/subsites/host-map.php'; + } + $hostmap = []; + + $subsites = DataObject::get(Subsite::class); + + if ($subsites) { + foreach ($subsites as $subsite) { + $domains = $subsite->Domains(); + if ($domains) { + foreach ($domains as $domain) { + $domainStr = $domain->Domain; + if (!self::$strict_subdomain_matching) { + $domainStr = preg_replace('/^www\./', '', $domainStr); + } + $hostmap[$domainStr] = $subsite->domain(); + } + } + if ($subsite->DefaultSite) { + $hostmap['default'] = $subsite->domain(); + } + } + } + + $data = "ID; + + // Count this user's groups which can access the main site + $groupCount = DB::query(" SELECT COUNT(\"Permission\".\"ID\") FROM \"Permission\" INNER JOIN \"Group\" ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1 @@ -476,8 +554,8 @@ public static function hasMainSitePermission($member = null, $permissionCodes = AND \"MemberID\" = {$memberID} ")->value(); - // Count this user's groups which have a role that can access the main site - $roleCount = DB::query(" + // Count this user's groups which have a role that can access the main site + $roleCount = DB::query(" SELECT COUNT(\"PermissionRoleCode\".\"ID\") FROM \"Group\" INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\" @@ -489,369 +567,388 @@ public static function hasMainSitePermission($member = null, $permissionCodes = AND \"MemberID\" = {$memberID} ")->value(); - // There has to be at least one that allows access. - return ($groupCount + $roleCount > 0); - } - - /** - * - * @var array - */ - private static $db = array( - 'Title' => 'Varchar(255)', - 'RedirectURL' => 'Varchar(255)', - 'DefaultSite' => 'Boolean', - 'Theme' => 'Varchar', - 'Language' => 'Varchar(6)', - - // Used to hide unfinished/private subsites from public view. - // If unset, will default to true - 'IsPublic' => 'Boolean', - - // Comma-separated list of disallowed page types - 'PageTypeBlacklist' => 'Text', - ); - - /** - * - * @var array - */ - private static $has_many = array( - 'Domains' => SubsiteDomain::class, - ); - - /** - * - * @var array - */ - private static $belongs_many_many = array( - "Groups" => "SilverStripe\\Security\\Group", - ); - - /** - * - * @var array - */ - private static $defaults = array( - 'IsPublic' => 1 - ); - - /** - * - * @var array - */ - private static $searchable_fields = array( - 'Title', - 'Domains.Domain', - 'IsPublic', - ); - - /** - * - * @var string - */ - private static $default_sort = "\"Title\" ASC"; - - /** - * @todo Possible security issue, don't grant edit permissions to everybody. - * @return boolean - */ - public function canEdit($member = false) { - return true; - } - - /** - * Show the configuration fields for each subsite - * - * @return FieldList - */ - public function getCMSFields() { - if($this->ID!=0) { - $domainTable = new GridField( - "Domains", - _t('Subsite.DomainsListTitle',"Domains"), - $this->Domains(), - GridFieldConfig_RecordEditor::create(10) - ); - }else { - $domainTable = new LiteralField( - 'Domains', - '

'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'

' - ); - } - - $languageSelector = new DropdownField( - 'Language', - $this->fieldLabel('Language'), + // There has to be at least one that allows access. + return ($groupCount + $roleCount > 0); + } + + /** + * + * @var array + */ + private static $db = [ + 'Title' => 'Varchar(255)', + 'RedirectURL' => 'Varchar(255)', + 'DefaultSite' => 'Boolean', + 'Theme' => 'Varchar', + 'Language' => 'Varchar(6)', + + // Used to hide unfinished/private subsites from public view. + // If unset, will default to true + 'IsPublic' => 'Boolean', + + // Comma-separated list of disallowed page types + 'PageTypeBlacklist' => 'Text', + ]; + + /** + * + * @var array + */ + private static $has_many = [ + 'Domains' => SubsiteDomain::class, + ]; + + /** + * + * @var array + */ + private static $belongs_many_many = [ + "Groups" => "SilverStripe\\Security\\Group", + ]; + + /** + * + * @var array + */ + private static $defaults = [ + 'IsPublic' => 1 + ]; + + /** + * + * @var array + */ + private static $searchable_fields = [ + 'Title', + 'Domains.Domain', + 'IsPublic', + ]; + + /** + * + * @var string + */ + private static $default_sort = "\"Title\" ASC"; + + /** + * @todo Possible security issue, don't grant edit permissions to everybody. + * @return boolean + */ + public function canEdit($member = false) + { + return true; + } + + /** + * Show the configuration fields for each subsite + * + * @return FieldList + */ + public function getCMSFields() + { + if ($this->ID != 0) { + $domainTable = new GridField( + "Domains", + _t('Subsite.DomainsListTitle', "Domains"), + $this->Domains(), + GridFieldConfig_RecordEditor::create(10) + ); + } else { + $domainTable = new LiteralField( + 'Domains', + '

' . _t('Subsite.DOMAINSAVEFIRST', + 'You can only add domains after saving for the first time') . '

' + ); + } + + $languageSelector = new DropdownField( + 'Language', + $this->fieldLabel('Language'), (new IntlLocales)->getLocales() - ); - - $pageTypeMap = array(); - $pageTypes = SiteTree::page_type_classes(); - foreach($pageTypes as $pageType) { - $pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name(); - } - asort($pageTypeMap); - - $fields = new FieldList( - $subsiteTabs = new TabSet('Root', - new Tab( - 'Configuration', - _t('Subsite.TabTitleConfig', 'Configuration'), - new HeaderField('ConfigurationHeader', $this->getClassName() . ' configuration', 2), - new TextField('Title', $this->fieldLabel('Title'), $this->Title), - - new HeaderField( + ); + + $pageTypeMap = []; + $pageTypes = SiteTree::page_type_classes(); + foreach ($pageTypes as $pageType) { + $pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name(); + } + asort($pageTypeMap); + + $fields = new FieldList( + $subsiteTabs = new TabSet('Root', + new Tab( + 'Configuration', + _t('Subsite.TabTitleConfig', 'Configuration'), + new HeaderField('ConfigurationHeader', $this->getClassName() . ' configuration', 2), + new TextField('Title', $this->fieldLabel('Title'), $this->Title), + + new HeaderField( 'DomainsHeader', - _t('Subsite.DomainsHeadline', "Domains for this subsite") - ), - $domainTable, - $languageSelector, - // new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL), - new CheckboxField('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite), - new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic), - - new DropdownField('Theme',$this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme), - - - new LiteralField( - 'PageTypeBlacklistToggle', - sprintf( - '', - _t('Subsite.PageTypeBlacklistField', 'Disallow page types?') - ) - ), - new CheckboxSetField( - 'PageTypeBlacklist', - false, - $pageTypeMap - ) - ) - ), - new HiddenField('ID', '', $this->ID), - new HiddenField('IsSubsite', '', 1) - ); - - $subsiteTabs->addExtraClass('subsite-model'); - - $this->extend('updateCMSFields', $fields); - return $fields; - } - - /** - * - * @param boolean $includerelations - * @return array - */ - public function fieldLabels($includerelations = true) { - $labels = parent::fieldLabels($includerelations); - $labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name'); - $labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL'); - $labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site'); - $labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme'); - $labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language'); - $labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access'); - $labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist'); - $labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain'); - $labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain'); - - return $labels; - } - - /** - * Return the themes that can be used with this subsite, as an array of themecode => description - * - * @return array - */ - public function allowedThemes() { - if($themes = $this->stat('allowed_themes')) { - return ArrayLib::valuekey($themes); - } else { - $themes = array(); - if(is_dir('../themes/')) { - foreach(scandir('../themes/') as $theme) { - if($theme[0] == '.') continue; - $theme = strtok($theme,'_'); - $themes[$theme] = $theme; - } - ksort($themes); - } - return $themes; - } - } - - /** - * @return string Current locale of the subsite - */ - public function getLanguage() { - if($this->getField('Language')) { - return $this->getField('Language'); - } else { - return i18n::get_locale(); - } - } - - /** - * - * @return ValidationResult - */ - public function validate() { - $result = parent::validate(); - if(!$this->Title) { - $result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"')); - } - return $result; - } - - /** - * Whenever a Subsite is written, rewrite the hostmap - * - * @return void - */ - public function onAfterWrite() { - Subsite::writeHostMap(); - parent::onAfterWrite(); - } - - /** - * Return the primary domain of this site. Tries to "normalize" the domain name, - * by replacing potential wildcards. - * - * @return string The full domain name of this subsite (without protocol prefix) - */ - public function domain() { - if($this->ID) { - $domains = DataObject::get(SubsiteDomain::class, "\"SubsiteID\" = $this->ID", "\"IsPrimary\" DESC","", 1); - if($domains && $domains->Count()>0) { - $domain = $domains->First()->Domain; - // If there are wildcards in the primary domain (not recommended), make some - // educated guesses about what to replace them with: - $domain = preg_replace('/\.\*$/',".$_SERVER[HTTP_HOST]", $domain); - // Default to "subsite." prefix for first wildcard - // TODO Whats the significance of "subsite" in this context?! - $domain = preg_replace('/^\*\./',"subsite.", $domain); - // *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com' - $domain = str_replace('.www.','.', $domain); - - return $domain; - } - - // SubsiteID = 0 is often used to refer to the main site, just return $_SERVER['HTTP_HOST'] - } else { - return $_SERVER['HTTP_HOST']; - } - } - - /** - * - * @return string - The full domain name of this subsite (without protocol prefix) - */ - public function getPrimaryDomain() { - return $this->domain(); - } - - /** - * - * @return string - */ - public function absoluteBaseURL() { - return "http://" . $this->domain() . Director::baseURL(); - } - - /** - * @todo getClassName is redundant, already stored as a database field? - */ - public function getClassName() { - return $this->class; - } - - /** - * Javascript admin action to duplicate this subsite - * - * @return string - javascript - */ - public function adminDuplicate() { - $newItem = $this->duplicate(); - $message = _t( - 'Subsite.CopyMessage', - 'Created a copy of {title}', - array('title' => Convert::raw2js($this->Title)) - ); - - return <<RedirectURL), + new CheckboxField('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite), + new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic), + + new DropdownField('Theme', $this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme), + + + new LiteralField( + 'PageTypeBlacklistToggle', + sprintf( + '', + _t('Subsite.PageTypeBlacklistField', 'Disallow page types?') + ) + ), + new CheckboxSetField( + 'PageTypeBlacklist', + false, + $pageTypeMap + ) + ) + ), + new HiddenField('ID', '', $this->ID), + new HiddenField('IsSubsite', '', 1) + ); + + $subsiteTabs->addExtraClass('subsite-model'); + + $this->extend('updateCMSFields', $fields); + return $fields; + } + + /** + * + * @param boolean $includerelations + * @return array + */ + public function fieldLabels($includerelations = true) + { + $labels = parent::fieldLabels($includerelations); + $labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name'); + $labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL'); + $labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site'); + $labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme'); + $labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language'); + $labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access'); + $labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist'); + $labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain'); + $labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain'); + + return $labels; + } + + /** + * Return the themes that can be used with this subsite, as an array of themecode => description + * + * @return array + */ + public function allowedThemes() + { + if ($themes = $this->stat('allowed_themes')) { + return ArrayLib::valuekey($themes); + } else { + $themes = []; + if (is_dir('../themes/')) { + foreach (scandir('../themes/') as $theme) { + if ($theme[0] == '.') { + continue; + } + $theme = strtok($theme, '_'); + $themes[$theme] = $theme; + } + ksort($themes); + } + return $themes; + } + } + + /** + * @return string Current locale of the subsite + */ + public function getLanguage() + { + if ($this->getField('Language')) { + return $this->getField('Language'); + } else { + return i18n::get_locale(); + } + } + + /** + * + * @return ValidationResult + */ + public function validate() + { + $result = parent::validate(); + if (!$this->Title) { + $result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"')); + } + return $result; + } + + /** + * Whenever a Subsite is written, rewrite the hostmap + * + * @return void + */ + public function onAfterWrite() + { + Subsite::writeHostMap(); + parent::onAfterWrite(); + } + + /** + * Return the primary domain of this site. Tries to "normalize" the domain name, + * by replacing potential wildcards. + * + * @return string The full domain name of this subsite (without protocol prefix) + */ + public function domain() + { + if ($this->ID) { + $domains = DataObject::get(SubsiteDomain::class, "\"SubsiteID\" = $this->ID", "\"IsPrimary\" DESC", "", 1); + if ($domains && $domains->Count() > 0) { + $domain = $domains->First()->Domain; + // If there are wildcards in the primary domain (not recommended), make some + // educated guesses about what to replace them with: + $domain = preg_replace('/\.\*$/', ".$_SERVER[HTTP_HOST]", $domain); + // Default to "subsite." prefix for first wildcard + // TODO Whats the significance of "subsite" in this context?! + $domain = preg_replace('/^\*\./', "subsite.", $domain); + // *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com' + $domain = str_replace('.www.', '.', $domain); + + return $domain; + } + + // SubsiteID = 0 is often used to refer to the main site, just return $_SERVER['HTTP_HOST'] + } else { + return $_SERVER['HTTP_HOST']; + } + } + + /** + * + * @return string - The full domain name of this subsite (without protocol prefix) + */ + public function getPrimaryDomain() + { + return $this->domain(); + } + + /** + * + * @return string + */ + public function absoluteBaseURL() + { + return "http://" . $this->domain() . Director::baseURL(); + } + + /** + * @todo getClassName is redundant, already stored as a database field? + */ + public function getClassName() + { + return $this->class; + } + + /** + * Javascript admin action to duplicate this subsite + * + * @return string - javascript + */ + public function adminDuplicate() + { + $newItem = $this->duplicate(); + $message = _t( + 'Subsite.CopyMessage', + 'Created a copy of {title}', + ['title' => Convert::raw2js($this->Title)] + ); + + return <<ID'); JS; - } - - /** - * Make this subsite the current one - */ - public function activate() { - Subsite::changeSubsite($this); - } - - /** - * - * @param array $permissionCodes - * @return DataList - */ - public function getMembersByPermission($permissionCodes = array('ADMIN')){ - if(!is_array($permissionCodes)) - user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR); - $SQL_permissionCodes = Convert::raw2sql($permissionCodes); - - $SQL_permissionCodes = join("','", $SQL_permissionCodes); - - return DataObject::get( - 'SilverStripe\\Security\\Member', - "\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')", - '', - "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\" + } + + /** + * Make this subsite the current one + */ + public function activate() + { + Subsite::changeSubsite($this); + } + + /** + * + * @param array $permissionCodes + * @return DataList + */ + public function getMembersByPermission($permissionCodes = ['ADMIN']) + { + if (!is_array($permissionCodes)) { + user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR); + } + $SQL_permissionCodes = Convert::raw2sql($permissionCodes); + + $SQL_permissionCodes = join("','", $SQL_permissionCodes); + + return DataObject::get( + 'SilverStripe\\Security\\Member', + "\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')", + '', + "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\" LEFT JOIN \"Group\" ON \"Group\".\"ID\" = \"Group_Members\".\"GroupID\" LEFT JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\"" - ); - - } - - /** - * Duplicate this subsite - */ - public function duplicate($doWrite = true) { - $duplicate = parent::duplicate($doWrite); - - $oldSubsiteID = Session::get('SubsiteID'); - self::changeSubsite($this->ID); - - /* - * Copy data from this object to the given subsite. Does this using an iterative depth-first search. - * This will make sure that the new parents on the new subsite are correct, and there are no funny - * issues with having to check whether or not the new parents have been added to the site tree - * when a page, etc, is duplicated - */ - $stack = array(array(0,0)); - while(count($stack) > 0) { - list($sourceParentID, $destParentID) = array_pop($stack); - $children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", ''); - - if($children) { - foreach($children as $child) { - self::changeSubsite($duplicate->ID); //Change to destination subsite - - $childClone = $child->duplicateToSubsite($duplicate, false); - $childClone->ParentID = $destParentID; - $childClone->writeToStage('Stage'); - $childClone->publish('Stage', 'Live'); - - self::changeSubsite($this->ID); //Change Back to this subsite - - array_push($stack, array($child->ID, $childClone->ID)); - } - } - } - - self::changeSubsite($oldSubsiteID); - - return $duplicate; - } + ); + + } + + /** + * Duplicate this subsite + */ + public function duplicate($doWrite = true) + { + $duplicate = parent::duplicate($doWrite); + + $oldSubsiteID = Session::get('SubsiteID'); + self::changeSubsite($this->ID); + + /* + * Copy data from this object to the given subsite. Does this using an iterative depth-first search. + * This will make sure that the new parents on the new subsite are correct, and there are no funny + * issues with having to check whether or not the new parents have been added to the site tree + * when a page, etc, is duplicated + */ + $stack = [[0, 0]]; + while (count($stack) > 0) { + list($sourceParentID, $destParentID) = array_pop($stack); + $children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", ''); + + if ($children) { + foreach ($children as $child) { + self::changeSubsite($duplicate->ID); //Change to destination subsite + + $childClone = $child->duplicateToSubsite($duplicate, false); + $childClone->ParentID = $destParentID; + $childClone->writeToStage('Stage'); + $childClone->publish('Stage', 'Live'); + + self::changeSubsite($this->ID); //Change Back to this subsite + + array_push($stack, [$child->ID, $childClone->ID]); + } + } + } + + self::changeSubsite($oldSubsiteID); + + return $duplicate; + } } diff --git a/code/model/SubsiteDomain.php b/code/model/SubsiteDomain.php index 0e71cbc1..79505c43 100644 --- a/code/model/SubsiteDomain.php +++ b/code/model/SubsiteDomain.php @@ -3,98 +3,102 @@ namespace SilverStripe\Subsites\Model; -use SilverStripe\Forms\TextField; +use SilverStripe\Core\Convert; use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\FieldList; -use SilverStripe\Core\Convert; +use SilverStripe\Forms\TextField; use SilverStripe\ORM\DataObject; - /** * @property text Domain domain name of this subsite. Do not include the URL scheme here * @property bool IsPrimary Is this the primary subdomain? */ -class SubsiteDomain extends DataObject { +class SubsiteDomain extends DataObject +{ private static $table_name = 'SubsiteDomain'; /** - * - * @var string - */ - private static $default_sort = "\"IsPrimary\" DESC"; - - /** - * - * @var array - */ - private static $db = array( - "Domain" => "Varchar(255)", - "IsPrimary" => "Boolean", - ); - - /** - * - * @var array - */ - private static $has_one = array( - "Subsite" => Subsite::class, - ); - - /** - * - * @var array - */ - private static $summary_fields=array( - 'Domain', - 'IsPrimary', - ); - - /** - * Whenever a Subsite Domain is written, rewrite the hostmap - * - * @return void - */ - public function onAfterWrite() { - Subsite::writeHostMap(); - } - - /** - * - * @return \FieldList - */ - public function getCMSFields() { - $fields = new FieldList( - new TextField('Domain', $this->fieldLabel('Domain'), null, 255), - new CheckboxField('IsPrimary', $this->fieldLabel('IsPrimary')) - ); - - $this->extend('updateCMSFields', $fields); - return $fields; - } - - /** - * - * @param bool $includerelations - * @return array - */ - public function fieldLabels($includerelations = true) { - $labels = parent::fieldLabels($includerelations); - $labels['Domain'] = _t('SubsiteDomain.DOMAIN', 'Domain'); - $labels['IsPrimary'] = _t('SubsiteDomain.IS_PRIMARY', 'Is Primary Domain'); - - return $labels; - } - - /** - * Before writing the Subsite Domain, strip out any HTML the user has entered. - * @return void - */ - public function onBeforeWrite() { - parent::onBeforeWrite(); - - //strip out any HTML to avoid XSS attacks - $this->Domain = Convert::html2raw($this->Domain); - } + * + * @var string + */ + private static $default_sort = "\"IsPrimary\" DESC"; + + /** + * + * @var array + */ + private static $db = [ + "Domain" => "Varchar(255)", + "IsPrimary" => "Boolean", + ]; + + /** + * + * @var array + */ + private static $has_one = [ + "Subsite" => Subsite::class, + ]; + + /** + * + * @var array + */ + private static $summary_fields = [ + 'Domain', + 'IsPrimary', + ]; + + /** + * Whenever a Subsite Domain is written, rewrite the hostmap + * + * @return void + */ + public function onAfterWrite() + { + Subsite::writeHostMap(); + } + + /** + * + * @return \FieldList + */ + public function getCMSFields() + { + $fields = new FieldList( + new TextField('Domain', $this->fieldLabel('Domain'), null, 255), + new CheckboxField('IsPrimary', $this->fieldLabel('IsPrimary')) + ); + + $this->extend('updateCMSFields', $fields); + return $fields; + } + + /** + * + * @param bool $includerelations + * @return array + */ + public function fieldLabels($includerelations = true) + { + $labels = parent::fieldLabels($includerelations); + $labels['Domain'] = _t('SubsiteDomain.DOMAIN', 'Domain'); + $labels['IsPrimary'] = _t('SubsiteDomain.IS_PRIMARY', 'Is Primary Domain'); + + return $labels; + } + + /** + * Before writing the Subsite Domain, strip out any HTML the user has entered. + * @return void + */ + public function onBeforeWrite() + { + parent::onBeforeWrite(); + + //strip out any HTML to avoid XSS attacks + $this->Domain = Convert::html2raw($this->Domain); + } } diff --git a/code/pages/SubsitesVirtualPage.php b/code/pages/SubsitesVirtualPage.php index 5e7d43b9..98cd522e 100644 --- a/code/pages/SubsitesVirtualPage.php +++ b/code/pages/SubsitesVirtualPage.php @@ -3,213 +3,234 @@ namespace SilverStripe\Subsites\Pages; -use SilverStripe\ORM\DataObject; -use SilverStripe\ORM\ArrayList; -use SilverStripe\View\ArrayData; -use SilverStripe\Forms\DropdownField; +use SilverStripe\CMS\Model\VirtualPage; use SilverStripe\Control\Controller; -use SilverStripe\Forms\LabelField; -use SilverStripe\Forms\TextField; -use SilverStripe\Forms\TextareaField; use SilverStripe\Control\Session; use SilverStripe\Core\Config\Config; -use SilverStripe\CMS\Model\VirtualPage; -use SilverStripe\Subsites\Model\Subsite; +use SilverStripe\Forms\DropdownField; +use SilverStripe\Forms\LabelField; +use SilverStripe\Forms\TextareaField; +use SilverStripe\Forms\TextField; +use SilverStripe\ORM\ArrayList; +use SilverStripe\ORM\DataObject; use SilverStripe\Subsites\Forms\SubsitesTreeDropdownField; +use SilverStripe\Subsites\Model\Subsite; +use SilverStripe\View\ArrayData; -class SubsitesVirtualPage extends VirtualPage { +class SubsitesVirtualPage extends VirtualPage +{ private static $table_name = 'SubsitesVirtualPage'; private static $description = 'Displays the content of a page on another subsite'; - private static $db = array( - 'CustomMetaTitle' => 'Varchar(255)', - 'CustomMetaKeywords' => 'Varchar(255)', - 'CustomMetaDescription' => 'Text', - 'CustomExtraMeta' => 'HTMLText' - ); - - public function getCMSFields() { - $fields = parent::getCMSFields(); - - $subsites = DataObject::get(Subsite::class); - if(!$subsites) { - $subsites = new ArrayList(); - }else { - $subsites=ArrayList::create($subsites->toArray()); - } - - $subsites->push(new ArrayData(array('Title' => 'Main site', 'ID' => 0))); - - $fields->addFieldToTab( - 'Root.Main', - DropdownField::create( - "CopyContentFromID_SubsiteID", - _t('SubsitesVirtualPage.SubsiteField',Subsite::class), - $subsites->map('ID', 'Title') - )->addExtraClass('subsitestreedropdownfield-chooser no-change-track'), - 'CopyContentFromID' - ); - - // Setup the linking to the original page. - $pageSelectionField = new SubsitesTreeDropdownField( - "CopyContentFromID", - _t('VirtualPage.CHOOSE', "Choose a page to link to"), - "SilverStripe\\CMS\\Model\\SiteTree", - "ID", - "MenuTitle" - ); - - if(Controller::has_curr() && Controller::curr()->getRequest()) { - $subsiteID = Controller::curr()->getRequest()->requestVar('CopyContentFromID_SubsiteID'); - $pageSelectionField->setSubsiteID($subsiteID); - } - $fields->replaceField('CopyContentFromID', $pageSelectionField); - - // Create links back to the original object in the CMS - if($this->CopyContentFromID) { - $editLink = "admin/pages/edit/show/$this->CopyContentFromID/?SubsiteID=" . $this->CopyContentFrom()->SubsiteID; - $linkToContent = " + private static $db = [ + 'CustomMetaTitle' => 'Varchar(255)', + 'CustomMetaKeywords' => 'Varchar(255)', + 'CustomMetaDescription' => 'Text', + 'CustomExtraMeta' => 'HTMLText' + ]; + + public function getCMSFields() + { + $fields = parent::getCMSFields(); + + $subsites = DataObject::get(Subsite::class); + if (!$subsites) { + $subsites = new ArrayList(); + } else { + $subsites = ArrayList::create($subsites->toArray()); + } + + $subsites->push(new ArrayData(['Title' => 'Main site', 'ID' => 0])); + + $fields->addFieldToTab( + 'Root.Main', + DropdownField::create( + "CopyContentFromID_SubsiteID", + _t('SubsitesVirtualPage.SubsiteField', Subsite::class), + $subsites->map('ID', 'Title') + )->addExtraClass('subsitestreedropdownfield-chooser no-change-track'), + 'CopyContentFromID' + ); + + // Setup the linking to the original page. + $pageSelectionField = new SubsitesTreeDropdownField( + "CopyContentFromID", + _t('VirtualPage.CHOOSE', "Choose a page to link to"), + "SilverStripe\\CMS\\Model\\SiteTree", + "ID", + "MenuTitle" + ); + + if (Controller::has_curr() && Controller::curr()->getRequest()) { + $subsiteID = Controller::curr()->getRequest()->requestVar('CopyContentFromID_SubsiteID'); + $pageSelectionField->setSubsiteID($subsiteID); + } + $fields->replaceField('CopyContentFromID', $pageSelectionField); + + // Create links back to the original object in the CMS + if ($this->CopyContentFromID) { + $editLink = "admin/pages/edit/show/$this->CopyContentFromID/?SubsiteID=" . $this->CopyContentFrom()->SubsiteID; + $linkToContent = " " . - _t('VirtualPage.EDITCONTENT', 'Click here to edit the content') . - ""; - $fields->removeByName("VirtualPageContentLinkLabel"); - $fields->addFieldToTab( - "Root.Main", - $linkToContentLabelField = new LabelField('VirtualPageContentLinkLabel', $linkToContent), - 'Title' - ); - $linkToContentLabelField->setAllowHTML(true); - } - - - $fields->addFieldToTab( - 'Root.Main', - TextField::create( - 'CustomMetaTitle', - $this->fieldLabel('CustomMetaTitle') - )->setDescription(_t('SubsitesVirtualPage.OverrideNote', 'Overrides inherited value from the source')), - 'MetaTitle' - ); - $fields->addFieldToTab( - 'Root.Main', - TextareaField::create( - 'CustomMetaKeywords', - $this->fieldLabel('CustomMetaTitle') - )->setDescription(_t('SubsitesVirtualPage.OverrideNote')), - 'MetaKeywords' - ); - $fields->addFieldToTab( - 'Root.Main', - TextareaField::create( - 'CustomMetaDescription', - $this->fieldLabel('CustomMetaTitle') - )->setDescription(_t('SubsitesVirtualPage.OverrideNote')), - 'MetaDescription' - ); - $fields->addFieldToTab( - 'Root.Main', - TextField::create( - 'CustomExtraMeta', - $this->fieldLabel('CustomMetaTitle') - )->setDescription(_t('SubsitesVirtualPage.OverrideNote')), - 'ExtraMeta' - ); - - return $fields; - } - - public function fieldLabels($includerelations = true) { - $labels = parent::fieldLabels($includerelations); - $labels['CustomMetaTitle'] = _t('Subsite.CustomMetaTitle','Title'); - $labels['CustomMetaKeywords'] = _t('Subsite.CustomMetaKeywords','Keywords'); - $labels['CustomMetaDescription'] = _t('Subsite.CustomMetaDescription','Description'); - $labels['CustomExtraMeta'] = _t('Subsite.CustomExtraMeta','Custom Meta Tags'); - - return $labels; - } - - public function getCopyContentFromID_SubsiteID() { - return ($this->CopyContentFromID) ? (int)$this->CopyContentFrom()->SubsiteID : (int)Session::get('SubsiteID'); - } - - public function getVirtualFields() { - $fields = parent::getVirtualFields(); - foreach($fields as $k => $v) { - if($v == 'SubsiteID') unset($fields[$k]); - } - - foreach(self::$db as $field => $type) if (in_array($field, $fields)) unset($fields[array_search($field, $fields)]); - - return $fields; - } - - public function syncLinkTracking() { - $oldState = Subsite::$disable_subsite_filter; - Subsite::$disable_subsite_filter = true; - if ($this->CopyContentFromID) $this->HasBrokenLink = DataObject::get_by_id('SilverStripe\\CMS\\Model\\SiteTree', $this->CopyContentFromID) ? false : true; - Subsite::$disable_subsite_filter = $oldState; - } - - public function onBeforeWrite() { - parent::onBeforeWrite(); - - if($this->CustomMetaTitle) $this->MetaTitle = $this->CustomMetaTitle; - else { - $this->MetaTitle = $this->ContentSource()->MetaTitle ? $this->ContentSource()->MetaTitle : $this->MetaTitle; - } - if($this->CustomMetaKeywords) $this->MetaKeywords = $this->CustomMetaKeywords; - else { - $this->MetaKeywords = $this->ContentSource()->MetaKeywords ? $this->ContentSource()->MetaKeywords : $this->MetaKeywords; - } - if($this->CustomMetaDescription) $this->MetaDescription = $this->CustomMetaDescription; - else { - $this->MetaDescription = $this->ContentSource()->MetaDescription ? $this->ContentSource()->MetaDescription : $this->MetaDescription; - } - if($this->CustomExtraMeta) $this->ExtraMeta = $this->CustomExtraMeta; - else { - $this->ExtraMeta = $this->ContentSource()->ExtraMeta ? $this->ContentSource()->ExtraMeta : $this->ExtraMeta; - } - } - - public function validURLSegment() { - $isValid = parent::validURLSegment(); - - // Veto the validation rules if its false. In this case, some logic - // needs to be duplicated from parent to find out the exact reason the validation failed. - if(!$isValid) { - $IDFilter = ($this->ID) ? "AND \"SiteTree\".\"ID\" <> $this->ID" : null; - $parentFilter = null; - - if(Config::inst()->get('SilverStripe\\CMS\\Model\\SiteTree', 'nested_urls')) { - if($this->ParentID) { - $parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID"; - } else { - $parentFilter = ' AND "SiteTree"."ParentID" = 0'; - } - } - - $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; - Subsite::$disable_subsite_filter = true; - $existingPage = DataObject::get_one( - 'SilverStripe\\CMS\\Model\\SiteTree', - "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter", - false // disable cache, it doesn't include subsite status in the key - ); - Subsite::$disable_subsite_filter = $origDisableSubsiteFilter; - $existingPageInSubsite = DataObject::get_one( - 'SilverStripe\\CMS\\Model\\SiteTree', - "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter", - false // disable cache, it doesn't include subsite status in the key - ); - - // If URL has been vetoed because of an existing page, - // be more specific and allow same URLSegments in different subsites - $isValid = !($existingPage && $existingPageInSubsite); - } - - return $isValid; - } + _t('VirtualPage.EDITCONTENT', 'Click here to edit the content') . + ""; + $fields->removeByName("VirtualPageContentLinkLabel"); + $fields->addFieldToTab( + "Root.Main", + $linkToContentLabelField = new LabelField('VirtualPageContentLinkLabel', $linkToContent), + 'Title' + ); + $linkToContentLabelField->setAllowHTML(true); + } + + + $fields->addFieldToTab( + 'Root.Main', + TextField::create( + 'CustomMetaTitle', + $this->fieldLabel('CustomMetaTitle') + )->setDescription(_t('SubsitesVirtualPage.OverrideNote', 'Overrides inherited value from the source')), + 'MetaTitle' + ); + $fields->addFieldToTab( + 'Root.Main', + TextareaField::create( + 'CustomMetaKeywords', + $this->fieldLabel('CustomMetaTitle') + )->setDescription(_t('SubsitesVirtualPage.OverrideNote')), + 'MetaKeywords' + ); + $fields->addFieldToTab( + 'Root.Main', + TextareaField::create( + 'CustomMetaDescription', + $this->fieldLabel('CustomMetaTitle') + )->setDescription(_t('SubsitesVirtualPage.OverrideNote')), + 'MetaDescription' + ); + $fields->addFieldToTab( + 'Root.Main', + TextField::create( + 'CustomExtraMeta', + $this->fieldLabel('CustomMetaTitle') + )->setDescription(_t('SubsitesVirtualPage.OverrideNote')), + 'ExtraMeta' + ); + + return $fields; + } + + public function fieldLabels($includerelations = true) + { + $labels = parent::fieldLabels($includerelations); + $labels['CustomMetaTitle'] = _t('Subsite.CustomMetaTitle', 'Title'); + $labels['CustomMetaKeywords'] = _t('Subsite.CustomMetaKeywords', 'Keywords'); + $labels['CustomMetaDescription'] = _t('Subsite.CustomMetaDescription', 'Description'); + $labels['CustomExtraMeta'] = _t('Subsite.CustomExtraMeta', 'Custom Meta Tags'); + + return $labels; + } + + public function getCopyContentFromID_SubsiteID() + { + return ($this->CopyContentFromID) ? (int)$this->CopyContentFrom()->SubsiteID : (int)Session::get('SubsiteID'); + } + + public function getVirtualFields() + { + $fields = parent::getVirtualFields(); + foreach ($fields as $k => $v) { + if ($v == 'SubsiteID') { + unset($fields[$k]); + } + } + + foreach (self::$db as $field => $type) { + if (in_array($field, $fields)) { + unset($fields[array_search($field, $fields)]); + } + } + + return $fields; + } + + public function syncLinkTracking() + { + $oldState = Subsite::$disable_subsite_filter; + Subsite::$disable_subsite_filter = true; + if ($this->CopyContentFromID) { + $this->HasBrokenLink = DataObject::get_by_id('SilverStripe\\CMS\\Model\\SiteTree', + $this->CopyContentFromID) ? false : true; + } + Subsite::$disable_subsite_filter = $oldState; + } + + public function onBeforeWrite() + { + parent::onBeforeWrite(); + + if ($this->CustomMetaTitle) { + $this->MetaTitle = $this->CustomMetaTitle; + } else { + $this->MetaTitle = $this->ContentSource()->MetaTitle ? $this->ContentSource()->MetaTitle : $this->MetaTitle; + } + if ($this->CustomMetaKeywords) { + $this->MetaKeywords = $this->CustomMetaKeywords; + } else { + $this->MetaKeywords = $this->ContentSource()->MetaKeywords ? $this->ContentSource()->MetaKeywords : $this->MetaKeywords; + } + if ($this->CustomMetaDescription) { + $this->MetaDescription = $this->CustomMetaDescription; + } else { + $this->MetaDescription = $this->ContentSource()->MetaDescription ? $this->ContentSource()->MetaDescription : $this->MetaDescription; + } + if ($this->CustomExtraMeta) { + $this->ExtraMeta = $this->CustomExtraMeta; + } else { + $this->ExtraMeta = $this->ContentSource()->ExtraMeta ? $this->ContentSource()->ExtraMeta : $this->ExtraMeta; + } + } + + public function validURLSegment() + { + $isValid = parent::validURLSegment(); + + // Veto the validation rules if its false. In this case, some logic + // needs to be duplicated from parent to find out the exact reason the validation failed. + if (!$isValid) { + $IDFilter = ($this->ID) ? "AND \"SiteTree\".\"ID\" <> $this->ID" : null; + $parentFilter = null; + + if (Config::inst()->get('SilverStripe\\CMS\\Model\\SiteTree', 'nested_urls')) { + if ($this->ParentID) { + $parentFilter = " AND \"SiteTree\".\"ParentID\" = $this->ParentID"; + } else { + $parentFilter = ' AND "SiteTree"."ParentID" = 0'; + } + } + + $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; + Subsite::$disable_subsite_filter = true; + $existingPage = DataObject::get_one( + 'SilverStripe\\CMS\\Model\\SiteTree', + "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter", + false // disable cache, it doesn't include subsite status in the key + ); + Subsite::$disable_subsite_filter = $origDisableSubsiteFilter; + $existingPageInSubsite = DataObject::get_one( + 'SilverStripe\\CMS\\Model\\SiteTree', + "\"URLSegment\" = '$this->URLSegment' $IDFilter $parentFilter", + false // disable cache, it doesn't include subsite status in the key + ); + + // If URL has been vetoed because of an existing page, + // be more specific and allow same URLSegments in different subsites + $isValid = !($existingPage && $existingPageInSubsite); + } + + return $isValid; + } } diff --git a/code/reports/SubsiteReportWrapper.php b/code/reports/SubsiteReportWrapper.php index 3f8cd29a..042afc32 100644 --- a/code/reports/SubsiteReportWrapper.php +++ b/code/reports/SubsiteReportWrapper.php @@ -3,73 +3,78 @@ namespace SilverStripe\Subsites\Reports; -use SilverStripe\Forms\TreeMultiselectField; use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\TreeMultiselectField; use SilverStripe\Reports\ReportWrapper; use SilverStripe\Subsites\Model\Subsite; - /** * Creates a subsite-aware version of another report. * Pass another report (or its classname) into the constructor. */ -class SubsiteReportWrapper extends ReportWrapper { - /////////////////////////////////////////////////////////////////////////////////////////// - // Filtering - - function parameterFields() { - $subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true); - $options = $subsites->toDropdownMap('ID', 'Title'); - - $subsiteField = new TreeMultiselectField( - 'Subsites', - _t('SubsiteReportWrapper.ReportDropdown', 'Sites'), - $options - ); - $subsiteField->setValue(array_keys($options)); - - // We don't need to make the field editable if only one subsite is available - if(sizeof($options) <= 1) { - $subsiteField = $subsiteField->performReadonlyTransformation(); - } - - $fields = parent::parameterFields(); - if($fields) { - $fields->insertBefore($subsiteField, $fields->First()->Name()); - } else { - $fields = new FieldList($subsiteField); - } - return $fields; - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Columns - - function columns() { - $columns = parent::columns(); - $columns['Subsite.Title'] = Subsite::class; - return $columns; - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Querying - - function beforeQuery($params) { - // The user has select a few specific sites - if(!empty($params['Subsites'])) { - Subsite::$force_subsite = $params['Subsites']; - - // Default: restrict to all accessible sites - } else { - $subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain'); - $options = $subsites->toDropdownMap('ID', 'Title'); - Subsite::$force_subsite = join(',', array_keys($options)); - } - } - function afterQuery() { - // Manually manage the subsite filtering - Subsite::$force_subsite = null; - } +class SubsiteReportWrapper extends ReportWrapper +{ + /////////////////////////////////////////////////////////////////////////////////////////// + // Filtering + + function parameterFields() + { + $subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true); + $options = $subsites->toDropdownMap('ID', 'Title'); + + $subsiteField = new TreeMultiselectField( + 'Subsites', + _t('SubsiteReportWrapper.ReportDropdown', 'Sites'), + $options + ); + $subsiteField->setValue(array_keys($options)); + + // We don't need to make the field editable if only one subsite is available + if (sizeof($options) <= 1) { + $subsiteField = $subsiteField->performReadonlyTransformation(); + } + + $fields = parent::parameterFields(); + if ($fields) { + $fields->insertBefore($subsiteField, $fields->First()->Name()); + } else { + $fields = new FieldList($subsiteField); + } + return $fields; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Columns + + function columns() + { + $columns = parent::columns(); + $columns['Subsite.Title'] = Subsite::class; + return $columns; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Querying + + function beforeQuery($params) + { + // The user has select a few specific sites + if (!empty($params['Subsites'])) { + Subsite::$force_subsite = $params['Subsites']; + + // Default: restrict to all accessible sites + } else { + $subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain'); + $options = $subsites->toDropdownMap('ID', 'Title'); + Subsite::$force_subsite = join(',', array_keys($options)); + } + } + + function afterQuery() + { + // Manually manage the subsite filtering + Subsite::$force_subsite = null; + } } diff --git a/code/tasks/SubsiteCopyPagesTask.php b/code/tasks/SubsiteCopyPagesTask.php index 288f561e..7da2274d 100644 --- a/code/tasks/SubsiteCopyPagesTask.php +++ b/code/tasks/SubsiteCopyPagesTask.php @@ -3,12 +3,12 @@ namespace SilverStripe\Subsites\Tasks; -use SilverStripe\ORM\DataObject; -use SilverStripe\Dev\BuildTask; use InvalidArgumentException; +use SilverStripe\Dev\BuildTask; +use SilverStripe\ORM\DataObject; use SilverStripe\Subsites\Model\Subsite; -use SilverStripe\Versioned\Versioned; use SilverStripe\Subsites\Pages\SubsitesVirtualPage; +use SilverStripe\Versioned\Versioned; /** @@ -18,62 +18,74 @@ * * Example: sake dev/tasks/SubsiteCopyPagesTask from= to= */ -class SubsiteCopyPagesTask extends BuildTask { - - protected $title = 'Copy pages to different subsite'; - - protected $description = ''; - - function run($request) { - $subsiteFromId = $request->getVar('from'); - if(!is_numeric($subsiteFromId)) throw new InvalidArgumentException('Missing "from" parameter'); - $subsiteFrom = DataObject::get_by_id(Subsite::class, $subsiteFromId); - if(!$subsiteFrom) throw new InvalidArgumentException('Subsite not found'); - - $subsiteToId = $request->getVar('to'); - if(!is_numeric($subsiteToId)) throw new InvalidArgumentException('Missing "to" parameter'); - $subsiteTo = DataObject::get_by_id(Subsite::class, $subsiteToId); - if(!$subsiteTo) throw new InvalidArgumentException('Subsite not found'); - - $useVirtualPages = (bool)$request->getVar('virtual'); - - Subsite::changeSubsite($subsiteFrom); - - // Copy data from this template to the given subsite. Does this using an iterative depth-first search. - // This will make sure that the new parents on the new subsite are correct, and there are no funny - // issues with having to check whether or not the new parents have been added to the site tree - // when a page, etc, is duplicated - $stack = array(array(0,0)); - while(count($stack) > 0) { - list($sourceParentID, $destParentID) = array_pop($stack); - - $children = Versioned::get_by_stage('SilverStripe\\CMS\\Model\\SiteTree', 'Live', "\"ParentID\" = $sourceParentID", ''); - - if($children) { - foreach($children as $child) { - if($useVirtualPages) { - $childClone = new SubsitesVirtualPage(); - $childClone->writeToStage('Stage'); - $childClone->CopyContentFromID = $child->ID; - $childClone->SubsiteID = $subsiteTo->ID; - } else { - $childClone = $child->duplicateToSubsite($subsiteTo->ID, true); - } - - $childClone->ParentID = $destParentID; - $childClone->writeToStage('Stage'); - $childClone->publish('Stage', 'Live'); - array_push($stack, array($child->ID, $childClone->ID)); - - $this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link())); - } - } - - unset($children); - } - } - - function log($msg) { - echo $msg . "\n"; - } +class SubsiteCopyPagesTask extends BuildTask +{ + + protected $title = 'Copy pages to different subsite'; + + protected $description = ''; + + function run($request) + { + $subsiteFromId = $request->getVar('from'); + if (!is_numeric($subsiteFromId)) { + throw new InvalidArgumentException('Missing "from" parameter'); + } + $subsiteFrom = DataObject::get_by_id(Subsite::class, $subsiteFromId); + if (!$subsiteFrom) { + throw new InvalidArgumentException('Subsite not found'); + } + + $subsiteToId = $request->getVar('to'); + if (!is_numeric($subsiteToId)) { + throw new InvalidArgumentException('Missing "to" parameter'); + } + $subsiteTo = DataObject::get_by_id(Subsite::class, $subsiteToId); + if (!$subsiteTo) { + throw new InvalidArgumentException('Subsite not found'); + } + + $useVirtualPages = (bool)$request->getVar('virtual'); + + Subsite::changeSubsite($subsiteFrom); + + // Copy data from this template to the given subsite. Does this using an iterative depth-first search. + // This will make sure that the new parents on the new subsite are correct, and there are no funny + // issues with having to check whether or not the new parents have been added to the site tree + // when a page, etc, is duplicated + $stack = [[0, 0]]; + while (count($stack) > 0) { + list($sourceParentID, $destParentID) = array_pop($stack); + + $children = Versioned::get_by_stage('SilverStripe\\CMS\\Model\\SiteTree', 'Live', + "\"ParentID\" = $sourceParentID", ''); + + if ($children) { + foreach ($children as $child) { + if ($useVirtualPages) { + $childClone = new SubsitesVirtualPage(); + $childClone->writeToStage('Stage'); + $childClone->CopyContentFromID = $child->ID; + $childClone->SubsiteID = $subsiteTo->ID; + } else { + $childClone = $child->duplicateToSubsite($subsiteTo->ID, true); + } + + $childClone->ParentID = $destParentID; + $childClone->writeToStage('Stage'); + $childClone->publish('Stage', 'Live'); + array_push($stack, [$child->ID, $childClone->ID]); + + $this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link())); + } + } + + unset($children); + } + } + + function log($msg) + { + echo $msg . "\n"; + } } diff --git a/tests/BaseSubsiteTest.php b/tests/BaseSubsiteTest.php index 77caac57..82109c25 100644 --- a/tests/BaseSubsiteTest.php +++ b/tests/BaseSubsiteTest.php @@ -3,30 +3,34 @@ use SilverStripe\Dev\SapphireTest; use SilverStripe\Subsites\Model\Subsite; -class BaseSubsiteTest extends SapphireTest { - - function setUp() { - parent::setUp(); - - Subsite::$use_session_subsiteid = true; - } - - /** - * Avoid subsites filtering on fixture fetching. - */ - function objFromFixture($class, $id) { - Subsite::disable_subsite_filter(true); - $obj = parent::objFromFixture($class, $id); - Subsite::disable_subsite_filter(false); - - return $obj; - } - - /** - * Tests the initial state of disable_subsite_filter - */ - function testDisableSubsiteFilter() { - $this->assertFalse(Subsite::$disable_subsite_filter); - } +class BaseSubsiteTest extends SapphireTest +{ + + function setUp() + { + parent::setUp(); + + Subsite::$use_session_subsiteid = true; + } + + /** + * Avoid subsites filtering on fixture fetching. + */ + function objFromFixture($class, $id) + { + Subsite::disable_subsite_filter(true); + $obj = parent::objFromFixture($class, $id); + Subsite::disable_subsite_filter(false); + + return $obj; + } + + /** + * Tests the initial state of disable_subsite_filter + */ + function testDisableSubsiteFilter() + { + $this->assertFalse(Subsite::$disable_subsite_filter); + } } diff --git a/tests/FileSubsitesTest.php b/tests/FileSubsitesTest.php index 3b6821ee..883bc6a4 100644 --- a/tests/FileSubsitesTest.php +++ b/tests/FileSubsitesTest.php @@ -1,83 +1,87 @@ assertTrue(is_array(singleton(FileSubsites::class)->extraStatics())); - $file = new File(); - $file->Name = 'FileTitle'; - $file->Title = 'FileTitle'; - $this->assertEquals(' * FileTitle', $file->alternateTreeTitle()); - $file->SubsiteID = $this->objFromFixture(Subsite::class, 'domaintest1')->ID; - $this->assertEquals('FileTitle', $file->getTreeTitle()); - $this->assertTrue(singleton('SilverStripe\\Assets\\Folder')->getCMSFields() instanceof FieldList); - Subsite::changeSubsite(1); - $this->assertEquals($file->cacheKeyComponent(), 'subsite-1'); - } - - function testWritingSubsiteID() { - $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')->logIn(); - - $subsite = $this->objFromFixture(Subsite::class, 'domaintest1'); - FileSubsites::$default_root_folders_global = true; - - Subsite::changeSubsite(0); - $file = new File(); - $file->write(); - $file->onAfterUpload(); - $this->assertEquals((int)$file->SubsiteID, 0); - - Subsite::changeSubsite($subsite->ID); - $this->assertTrue($file->canEdit()); - - $file = new File(); - $file->write(); - $this->assertEquals((int)$file->SubsiteID, 0); - $this->assertTrue($file->canEdit()); - - FileSubsites::$default_root_folders_global = false; - - Subsite::changeSubsite($subsite->ID); - $file = new File(); - $file->write(); - $this->assertEquals($file->SubsiteID, $subsite->ID); - - // Test inheriting from parent folder - $folder = new Folder(); - $folder->write(); - $this->assertEquals($folder->SubsiteID, $subsite->ID); - FileSubsites::$default_root_folders_global = true; - $file = new File(); - $file->ParentID = $folder->ID; - $file->onAfterUpload(); - $this->assertEquals($folder->SubsiteID, $file->SubsiteID); - } - - function testSubsitesFolderDropdown() { - $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')->logIn(); - - $file = new Folder(); - - $source = array_values($file->getCMSFields()->dataFieldByName('SubsiteID')->getSource()); - asort($source); - - $this->assertEquals(array( - 'Main site', - 'Template', - 'Subsite1 Template', - 'Subsite2 Template', - 'Test 1', - 'Test 2', - 'Test 3' - ), $source); - } - +class FileSubsitesTest extends BaseSubsiteTest +{ + static $fixture_file = 'subsites/tests/SubsiteTest.yml'; + + function testTrivialFeatures() + { + $this->assertTrue(is_array(singleton(FileSubsites::class)->extraStatics())); + $file = new File(); + $file->Name = 'FileTitle'; + $file->Title = 'FileTitle'; + $this->assertEquals(' * FileTitle', $file->alternateTreeTitle()); + $file->SubsiteID = $this->objFromFixture(Subsite::class, 'domaintest1')->ID; + $this->assertEquals('FileTitle', $file->getTreeTitle()); + $this->assertTrue(singleton('SilverStripe\\Assets\\Folder')->getCMSFields() instanceof FieldList); + Subsite::changeSubsite(1); + $this->assertEquals($file->cacheKeyComponent(), 'subsite-1'); + } + + function testWritingSubsiteID() + { + $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')->logIn(); + + $subsite = $this->objFromFixture(Subsite::class, 'domaintest1'); + FileSubsites::$default_root_folders_global = true; + + Subsite::changeSubsite(0); + $file = new File(); + $file->write(); + $file->onAfterUpload(); + $this->assertEquals((int)$file->SubsiteID, 0); + + Subsite::changeSubsite($subsite->ID); + $this->assertTrue($file->canEdit()); + + $file = new File(); + $file->write(); + $this->assertEquals((int)$file->SubsiteID, 0); + $this->assertTrue($file->canEdit()); + + FileSubsites::$default_root_folders_global = false; + + Subsite::changeSubsite($subsite->ID); + $file = new File(); + $file->write(); + $this->assertEquals($file->SubsiteID, $subsite->ID); + + // Test inheriting from parent folder + $folder = new Folder(); + $folder->write(); + $this->assertEquals($folder->SubsiteID, $subsite->ID); + FileSubsites::$default_root_folders_global = true; + $file = new File(); + $file->ParentID = $folder->ID; + $file->onAfterUpload(); + $this->assertEquals($folder->SubsiteID, $file->SubsiteID); + } + + function testSubsitesFolderDropdown() + { + $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')->logIn(); + + $file = new Folder(); + + $source = array_values($file->getCMSFields()->dataFieldByName('SubsiteID')->getSource()); + asort($source); + + $this->assertEquals([ + 'Main site', + 'Template', + 'Subsite1 Template', + 'Subsite2 Template', + 'Test 1', + 'Test 2', + 'Test 3' + ], $source); + } + } diff --git a/tests/GroupSubsitesTest.php b/tests/GroupSubsitesTest.php index e994d831..f7ca5d31 100644 --- a/tests/GroupSubsitesTest.php +++ b/tests/GroupSubsitesTest.php @@ -6,26 +6,29 @@ use SilverStripe\Subsites\Model\Subsite; -class GroupSubsitesTest extends BaseSubsiteTest { - static $fixture_file = 'subsites/tests/SubsiteTest.yml'; - - protected $requireDefaultRecordsFrom = array(GroupSubsites::class); - - function testTrivialFeatures() { - $this->assertTrue(is_array(singleton(GroupSubsites::class)->extraStatics())); - $this->assertTrue(is_array(singleton(GroupSubsites::class)->providePermissions())); - $this->assertTrue(singleton('SilverStripe\\Security\\Group')->getCMSFields() instanceof FieldList); - } - - function testAlternateTreeTitle() { - $group = new Group(); - $group->Title = 'The A Team'; - $group->AccessAllSubsites = true; - $this->assertEquals($group->getTreeTitle(), 'The A Team (global group)'); - $group->AccessAllSubsites = false; - $group->write(); - $group->Subsites()->add($this->objFromFixture(Subsite::class, 'domaintest1')); - $group->Subsites()->add($this->objFromFixture(Subsite::class, 'domaintest2')); - $this->assertEquals($group->getTreeTitle(), 'The A Team (Test 1, Test 2)'); - } -} \ No newline at end of file +class GroupSubsitesTest extends BaseSubsiteTest +{ + static $fixture_file = 'subsites/tests/SubsiteTest.yml'; + + protected $requireDefaultRecordsFrom = [GroupSubsites::class]; + + function testTrivialFeatures() + { + $this->assertTrue(is_array(singleton(GroupSubsites::class)->extraStatics())); + $this->assertTrue(is_array(singleton(GroupSubsites::class)->providePermissions())); + $this->assertTrue(singleton('SilverStripe\\Security\\Group')->getCMSFields() instanceof FieldList); + } + + function testAlternateTreeTitle() + { + $group = new Group(); + $group->Title = 'The A Team'; + $group->AccessAllSubsites = true; + $this->assertEquals($group->getTreeTitle(), 'The A Team (global group)'); + $group->AccessAllSubsites = false; + $group->write(); + $group->Subsites()->add($this->objFromFixture(Subsite::class, 'domaintest1')); + $group->Subsites()->add($this->objFromFixture(Subsite::class, 'domaintest2')); + $this->assertEquals($group->getTreeTitle(), 'The A Team (Test 1, Test 2)'); + } +} diff --git a/tests/LeftAndMainSubsitesTest.php b/tests/LeftAndMainSubsitesTest.php index 17572bcd..9f38e8ba 100644 --- a/tests/LeftAndMainSubsitesTest.php +++ b/tests/LeftAndMainSubsitesTest.php @@ -1,95 +1,102 @@ objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); - - $cmsmain = singleton('SilverStripe\\CMS\\Controllers\\CMSMain'); - $subsites = $cmsmain->sectionSites(true, "Main site", $member); - $this->assertDOSEquals(array( - array('Title' =>'Subsite1 Template') - ), $subsites, 'Lists member-accessible sites for the accessible controller.'); - - $assetadmin = singleton(AssetAdmin::class); - $subsites = $assetadmin->sectionSites(true, "Main site", $member); - $this->assertDOSEquals(array(), $subsites, 'Does not list any sites for forbidden controller.'); - - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); - - $cmsmain = singleton('SilverStripe\\CMS\\Controllers\\CMSMain'); - $subsites = $cmsmain->sectionSites(true, "Main site", $member); - $this->assertDOSContains(array( - array('Title' =>'Main site') - ), $subsites, 'Includes the main site for members who can access all sites.'); - } - - function testAccessChecksDontChangeCurrentSubsite() { - $admin = $this->objFromFixture("SilverStripe\\Security\\Member","admin"); - $this->loginAs($admin); - $ids = array(); - - $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); - $subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2'); - $subsite3 = $this->objFromFixture(Subsite::class, 'domaintest3'); - - $ids[] = $subsite1->ID; - $ids[] = $subsite2->ID; - $ids[] = $subsite3->ID; - $ids[] = 0; - - // Enable session-based subsite tracking. - Subsite::$use_session_subsiteid = true; - - foreach($ids as $id) { - Subsite::changeSubsite($id); - $this->assertEquals($id, Subsite::currentSubsiteID()); - - $left = new LeftAndMain(); - $this->assertTrue($left->canView(), "Admin user can view subsites LeftAndMain with id = '$id'"); - $this->assertEquals($id, Subsite::currentSubsiteID(), - "The current subsite has not been changed in the process of checking permissions for admin user."); - } - - } - - function testShouldChangeSubsite() { - $l = new LeftAndMain(); - Config::inst()->nest(); - - Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', false); - $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 0)); - $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 1)); - Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', true); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 0)); - $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 1)); - Config::inst()->unnest(); - } +class LeftAndMainSubsitesTest extends FunctionalTest +{ + + static $fixture_file = 'subsites/tests/SubsiteTest.yml'; + + /** + * Avoid subsites filtering on fixture fetching. + */ + function objFromFixture($class, $id) + { + Subsite::disable_subsite_filter(true); + $obj = parent::objFromFixture($class, $id); + Subsite::disable_subsite_filter(false); + + return $obj; + } + + function testSectionSites() + { + $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + + $cmsmain = singleton('SilverStripe\\CMS\\Controllers\\CMSMain'); + $subsites = $cmsmain->sectionSites(true, "Main site", $member); + $this->assertDOSEquals([ + ['Title' => 'Subsite1 Template'] + ], $subsites, 'Lists member-accessible sites for the accessible controller.'); + + $assetadmin = singleton(AssetAdmin::class); + $subsites = $assetadmin->sectionSites(true, "Main site", $member); + $this->assertDOSEquals([], $subsites, 'Does not list any sites for forbidden controller.'); + + $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); + + $cmsmain = singleton('SilverStripe\\CMS\\Controllers\\CMSMain'); + $subsites = $cmsmain->sectionSites(true, "Main site", $member); + $this->assertDOSContains([ + ['Title' => 'Main site'] + ], $subsites, 'Includes the main site for members who can access all sites.'); + } + + function testAccessChecksDontChangeCurrentSubsite() + { + $admin = $this->objFromFixture("SilverStripe\\Security\\Member", "admin"); + $this->loginAs($admin); + $ids = []; + + $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); + $subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2'); + $subsite3 = $this->objFromFixture(Subsite::class, 'domaintest3'); + + $ids[] = $subsite1->ID; + $ids[] = $subsite2->ID; + $ids[] = $subsite3->ID; + $ids[] = 0; + + // Enable session-based subsite tracking. + Subsite::$use_session_subsiteid = true; + + foreach ($ids as $id) { + Subsite::changeSubsite($id); + $this->assertEquals($id, Subsite::currentSubsiteID()); + + $left = new LeftAndMain(); + $this->assertTrue($left->canView(), "Admin user can view subsites LeftAndMain with id = '$id'"); + $this->assertEquals($id, Subsite::currentSubsiteID(), + "The current subsite has not been changed in the process of checking permissions for admin user."); + } + + } + + function testShouldChangeSubsite() + { + $l = new LeftAndMain(); + Config::inst()->nest(); + + Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', + false); + $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 5)); + $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 0)); + $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 5)); + $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 1)); + + Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', + true); + $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 5)); + $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 0)); + $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 5)); + $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 1)); + + Config::inst()->unnest(); + } } diff --git a/tests/SiteConfigSubsitesTest.php b/tests/SiteConfigSubsitesTest.php index f7f5d879..d1a933e7 100644 --- a/tests/SiteConfigSubsitesTest.php +++ b/tests/SiteConfigSubsitesTest.php @@ -1,43 +1,45 @@ objFromFixture(Subsite::class, 'domaintest1'); + $subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2'); + + $this->assertTrue(is_array(singleton(SiteConfigSubsites::class)->extraStatics())); + + Subsite::changeSubsite(0); + $sc = SiteConfig::current_site_config(); + $sc->Title = 'RootSite'; + $sc->write(); + + Subsite::changeSubsite($subsite1->ID); + $sc = SiteConfig::current_site_config(); + $sc->Title = 'Subsite1'; + $sc->write(); + + Subsite::changeSubsite($subsite2->ID); + $sc = SiteConfig::current_site_config(); + $sc->Title = 'Subsite2'; + $sc->write(); + Subsite::changeSubsite(0); + $this->assertEquals(SiteConfig::current_site_config()->Title, 'RootSite'); + Subsite::changeSubsite($subsite1->ID); + $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite1'); + Subsite::changeSubsite($subsite2->ID); + $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite2'); -class SiteConfigSubsitesTest extends BaseSubsiteTest { - static $fixture_file = 'subsites/tests/SubsiteTest.yml'; - - function testEachSubsiteHasAUniqueSiteConfig() { - $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); - $subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2'); - - $this->assertTrue(is_array(singleton(SiteConfigSubsites::class)->extraStatics())); - - Subsite::changeSubsite(0); - $sc = SiteConfig::current_site_config(); - $sc->Title = 'RootSite'; - $sc->write(); - - Subsite::changeSubsite($subsite1->ID); - $sc = SiteConfig::current_site_config(); - $sc->Title = 'Subsite1'; - $sc->write(); - - Subsite::changeSubsite($subsite2->ID); - $sc = SiteConfig::current_site_config(); - $sc->Title = 'Subsite2'; - $sc->write(); - - Subsite::changeSubsite(0); - $this->assertEquals(SiteConfig::current_site_config()->Title, 'RootSite'); - Subsite::changeSubsite($subsite1->ID); - $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite1'); - Subsite::changeSubsite($subsite2->ID); - $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite2'); - - $keys = SiteConfig::current_site_config()->extend('cacheKeyComponent'); - $this->assertContains('subsite-' . $subsite2->ID, $keys); - } + $keys = SiteConfig::current_site_config()->extend('cacheKeyComponent'); + $this->assertContains('subsite-' . $subsite2->ID, $keys); + } } diff --git a/tests/SiteTreeSubsitesTest.php b/tests/SiteTreeSubsitesTest.php index 484a77c9..40027d0e 100644 --- a/tests/SiteTreeSubsitesTest.php +++ b/tests/SiteTreeSubsitesTest.php @@ -1,229 +1,243 @@ array('Translatable') - ); - - function testPagesInDifferentSubsitesCanShareURLSegment() { - $subsiteMain = $this->objFromFixture(Subsite::class, 'main'); - $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); - - $pageMain = new SiteTree(); - $pageMain->URLSegment = 'testpage'; - $pageMain->write(); - $pageMain->publish('Stage', 'Live'); - - $pageMainOther = new SiteTree(); - $pageMainOther->URLSegment = 'testpage'; - $pageMainOther->write(); - $pageMainOther->publish('Stage', 'Live'); - - $this->assertNotEquals($pageMain->URLSegment, $pageMainOther->URLSegment, - 'Pages in same subsite cant share the same URL' - ); - - Subsite::changeSubsite($subsite1->ID); - - $pageSubsite1 = new SiteTree(); - $pageSubsite1->URLSegment = 'testpage'; - $pageSubsite1->write(); - $pageSubsite1->publish('Stage', 'Live'); - - $this->assertEquals($pageMain->URLSegment, $pageSubsite1->URLSegment, - 'Pages in different subsites can share the same URL' - ); - } - - function testBasicSanity() { - $this->assertTrue(singleton('SilverStripe\\CMS\\Model\\SiteTree')->getSiteConfig() instanceof SiteConfig); - // The following assert is breaking in Translatable. - $this->assertTrue(singleton('SilverStripe\\CMS\\Model\\SiteTree')->getCMSFields() instanceof FieldList); - $this->assertTrue(singleton(SubsitesVirtualPage::class)->getCMSFields() instanceof FieldList); - $this->assertTrue(is_array(singleton(SiteTreeSubsites::class)->extraStatics())); - } - - function testErrorPageLocations() { - $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); - - Subsite::changeSubsite($subsite1->ID); - $path = SiteTreeSubsitesTest_ErrorPage::get_error_filename_spy(500); - - $expected_path = 'error-500-'.$subsite1->domain().'.html'; - $this->assertEquals($expected_path, $path); - } - - function testCanEditSiteTree() { - $admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); - $subsite1member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); - $subsite2member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite2member'); - $mainpage = $this->objFromFixture('Page', 'home'); - $subsite1page = $this->objFromFixture('Page', 'subsite1_home'); - $subsite2page = $this->objFromFixture('Page', 'subsite2_home'); - $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); - $subsite2 = $this->objFromFixture(Subsite::class, 'subsite2'); - - // Cant pass member as arguments to canEdit() because of GroupSubsites - Session::set("loggedInAs", $admin->ID); - $this->assertTrue( - (bool)$subsite1page->canEdit(), - 'Administrators can edit all subsites' - ); - - // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state - Subsite::changeSubsite($subsite1); - - Session::set("loggedInAs", $subsite1member->ID); - $this->assertTrue( - (bool)$subsite1page->canEdit(), - 'Members can edit pages on a subsite if they are in a group belonging to this subsite' - ); - - Session::set("loggedInAs", $subsite2member->ID); - $this->assertFalse( - (bool)$subsite1page->canEdit(), - 'Members cant edit pages on a subsite if they are not in a group belonging to this subsite' - ); - - // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state - Subsite::changeSubsite(0); - $this->assertFalse( - $mainpage->canEdit(), - 'Members cant edit pages on the main site if they are not in a group allowing this' - ); - } - - /** - * Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}. - */ - function testTwoPagesWithSameURLOnDifferentSubsites() { - // Set up a couple of pages with the same URL on different subsites - $s1 = $this->objFromFixture(Subsite::class,'domaintest1'); - $s2 = $this->objFromFixture(Subsite::class,'domaintest2'); - - $p1 = new SiteTree(); - $p1->Title = $p1->URLSegment = "test-page"; - $p1->SubsiteID = $s1->ID; - $p1->write(); - - $p2 = new SiteTree(); - $p2->Title = $p1->URLSegment = "test-page"; - $p2->SubsiteID = $s2->ID; - $p2->write(); - - // Check that the URLs weren't modified in our set-up - $this->assertEquals($p1->URLSegment, 'test-page'); - $this->assertEquals($p2->URLSegment, 'test-page'); - - // Check that if we switch between the different subsites, we receive the correct pages - Subsite::changeSubsite($s1); - $this->assertEquals($p1->ID, SiteTree::get_by_link('test-page')->ID); - - Subsite::changeSubsite($s2); - $this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID); - } - - function testPageTypesBlacklistInClassDropdown() { - $editor = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); - Session::set("loggedInAs", $editor->ID); - - $s1 = $this->objFromFixture(Subsite::class,'domaintest1'); - $s2 = $this->objFromFixture(Subsite::class,'domaintest2'); - $page = singleton('SilverStripe\\CMS\\Model\\SiteTree'); - - $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; - $s1->write(); - - Subsite::changeSubsite($s1); - $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); - - $this->assertArrayNotHasKey('SilverStripe\\CMS\\Model\\ErrorPage', - $settingsFields - ); - $this->assertArrayNotHasKey('SiteTreeSubsitesTest_ClassA', - $settingsFields - ); - $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', - $settingsFields - ); - - Subsite::changeSubsite($s2); - $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); - $this->assertArrayHasKey('SilverStripe\\CMS\\Model\\ErrorPage', - $settingsFields - ); - $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassA', - $settingsFields - ); - $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', - $settingsFields - ); - } - - function testPageTypesBlacklistInCMSMain() { - $editor = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); - Session::set("loggedInAs", $editor->ID); - - $cmsmain = new CMSMain(); - - $s1 = $this->objFromFixture(Subsite::class,'domaintest1'); - $s2 = $this->objFromFixture(Subsite::class,'domaintest2'); - - $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; - $s1->write(); - - Subsite::changeSubsite($s1); - $hints = Convert::json2array($cmsmain->SiteTreeHints()); - $classes = $hints['Root']['disallowedChildren']; - $this->assertContains('SilverStripe\\CMS\\Model\\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('SilverStripe\\CMS\\Model\\ErrorPage', $classes); - $this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes); - $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); - } +class SiteTreeSubsitesTest extends BaseSubsiteTest +{ + + static $fixture_file = 'subsites/tests/SubsiteTest.yml'; + + protected $extraDataObjects = [ + 'SiteTreeSubsitesTest_ClassA', + 'SiteTreeSubsitesTest_ClassB', + 'SiteTreeSubsitesTest_ErrorPage' + ]; + + protected $illegalExtensions = [ + 'SilverStripe\CMS\Model\SiteTree' => ['Translatable'] + ]; + + function testPagesInDifferentSubsitesCanShareURLSegment() + { + $subsiteMain = $this->objFromFixture(Subsite::class, 'main'); + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + + $pageMain = new SiteTree(); + $pageMain->URLSegment = 'testpage'; + $pageMain->write(); + $pageMain->publish('Stage', 'Live'); + + $pageMainOther = new SiteTree(); + $pageMainOther->URLSegment = 'testpage'; + $pageMainOther->write(); + $pageMainOther->publish('Stage', 'Live'); + + $this->assertNotEquals($pageMain->URLSegment, $pageMainOther->URLSegment, + 'Pages in same subsite cant share the same URL' + ); + + Subsite::changeSubsite($subsite1->ID); + + $pageSubsite1 = new SiteTree(); + $pageSubsite1->URLSegment = 'testpage'; + $pageSubsite1->write(); + $pageSubsite1->publish('Stage', 'Live'); + + $this->assertEquals($pageMain->URLSegment, $pageSubsite1->URLSegment, + 'Pages in different subsites can share the same URL' + ); + } + + function testBasicSanity() + { + $this->assertTrue(singleton('SilverStripe\\CMS\\Model\\SiteTree')->getSiteConfig() instanceof SiteConfig); + // The following assert is breaking in Translatable. + $this->assertTrue(singleton('SilverStripe\\CMS\\Model\\SiteTree')->getCMSFields() instanceof FieldList); + $this->assertTrue(singleton(SubsitesVirtualPage::class)->getCMSFields() instanceof FieldList); + $this->assertTrue(is_array(singleton(SiteTreeSubsites::class)->extraStatics())); + } + + function testErrorPageLocations() + { + $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); + + Subsite::changeSubsite($subsite1->ID); + $path = SiteTreeSubsitesTest_ErrorPage::get_error_filename_spy(500); + + $expected_path = 'error-500-' . $subsite1->domain() . '.html'; + $this->assertEquals($expected_path, $path); + } + + function testCanEditSiteTree() + { + $admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); + $subsite1member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + $subsite2member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite2member'); + $mainpage = $this->objFromFixture('Page', 'home'); + $subsite1page = $this->objFromFixture('Page', 'subsite1_home'); + $subsite2page = $this->objFromFixture('Page', 'subsite2_home'); + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + $subsite2 = $this->objFromFixture(Subsite::class, 'subsite2'); + + // Cant pass member as arguments to canEdit() because of GroupSubsites + Session::set("loggedInAs", $admin->ID); + $this->assertTrue( + (bool)$subsite1page->canEdit(), + 'Administrators can edit all subsites' + ); + + // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state + Subsite::changeSubsite($subsite1); + + Session::set("loggedInAs", $subsite1member->ID); + $this->assertTrue( + (bool)$subsite1page->canEdit(), + 'Members can edit pages on a subsite if they are in a group belonging to this subsite' + ); + + Session::set("loggedInAs", $subsite2member->ID); + $this->assertFalse( + (bool)$subsite1page->canEdit(), + 'Members cant edit pages on a subsite if they are not in a group belonging to this subsite' + ); + + // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state + Subsite::changeSubsite(0); + $this->assertFalse( + $mainpage->canEdit(), + 'Members cant edit pages on the main site if they are not in a group allowing this' + ); + } + + /** + * Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}. + */ + function testTwoPagesWithSameURLOnDifferentSubsites() + { + // Set up a couple of pages with the same URL on different subsites + $s1 = $this->objFromFixture(Subsite::class, 'domaintest1'); + $s2 = $this->objFromFixture(Subsite::class, 'domaintest2'); + + $p1 = new SiteTree(); + $p1->Title = $p1->URLSegment = "test-page"; + $p1->SubsiteID = $s1->ID; + $p1->write(); + + $p2 = new SiteTree(); + $p2->Title = $p1->URLSegment = "test-page"; + $p2->SubsiteID = $s2->ID; + $p2->write(); + + // Check that the URLs weren't modified in our set-up + $this->assertEquals($p1->URLSegment, 'test-page'); + $this->assertEquals($p2->URLSegment, 'test-page'); + + // Check that if we switch between the different subsites, we receive the correct pages + Subsite::changeSubsite($s1); + $this->assertEquals($p1->ID, SiteTree::get_by_link('test-page')->ID); + + Subsite::changeSubsite($s2); + $this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID); + } + + function testPageTypesBlacklistInClassDropdown() + { + $editor = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); + Session::set("loggedInAs", $editor->ID); + + $s1 = $this->objFromFixture(Subsite::class, 'domaintest1'); + $s2 = $this->objFromFixture(Subsite::class, 'domaintest2'); + $page = singleton('SilverStripe\\CMS\\Model\\SiteTree'); + + $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; + $s1->write(); + + Subsite::changeSubsite($s1); + $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); + + $this->assertArrayNotHasKey('SilverStripe\\CMS\\Model\\ErrorPage', + $settingsFields + ); + $this->assertArrayNotHasKey('SiteTreeSubsitesTest_ClassA', + $settingsFields + ); + $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', + $settingsFields + ); + + Subsite::changeSubsite($s2); + $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); + $this->assertArrayHasKey('SilverStripe\\CMS\\Model\\ErrorPage', + $settingsFields + ); + $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassA', + $settingsFields + ); + $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', + $settingsFields + ); + } + + function testPageTypesBlacklistInCMSMain() + { + $editor = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); + Session::set("loggedInAs", $editor->ID); + + $cmsmain = new CMSMain(); + + $s1 = $this->objFromFixture(Subsite::class, 'domaintest1'); + $s2 = $this->objFromFixture(Subsite::class, 'domaintest2'); + + $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; + $s1->write(); + + Subsite::changeSubsite($s1); + $hints = Convert::json2array($cmsmain->SiteTreeHints()); + $classes = $hints['Root']['disallowedChildren']; + $this->assertContains('SilverStripe\\CMS\\Model\\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('SilverStripe\\CMS\\Model\\ErrorPage', $classes); + $this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes); + $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); + } } -class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly {} - -class SiteTreeSubsitesTest_ClassB extends SiteTree implements TestOnly {} +class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly +{ +} -class SiteTreeSubsitesTest_ErrorPage extends ErrorPage implements TestOnly { +class SiteTreeSubsitesTest_ClassB extends SiteTree implements TestOnly +{ +} - /** - * Helper method to call protected members - * - * @param int $statusCode - * @return string - */ - public static function get_error_filename_spy($statusCode) { - return self::get_error_filename($statusCode); - } +class SiteTreeSubsitesTest_ErrorPage extends ErrorPage implements TestOnly +{ + + /** + * Helper method to call protected members + * + * @param int $statusCode + * @return string + */ + public static function get_error_filename_spy($statusCode) + { + return self::get_error_filename($statusCode); + } } diff --git a/tests/SubsiteAdminFunctionalTest.php b/tests/SubsiteAdminFunctionalTest.php index a16dafe3..c4714e51 100644 --- a/tests/SubsiteAdminFunctionalTest.php +++ b/tests/SubsiteAdminFunctionalTest.php @@ -3,148 +3,160 @@ use SilverStripe\Control\Session; use SilverStripe\Core\Config\Config; use SilverStripe\Dev\FunctionalTest; -use SilverStripe\Subsites\Model\Subsite; use SilverStripe\Subsites\Controller\SubsiteXHRController; +use SilverStripe\Subsites\Model\Subsite; -class SubsiteAdminFunctionalTest extends FunctionalTest { - static $fixture_file = 'subsites/tests/SubsiteTest.yml'; - static $use_draft_site = true; - - protected $autoFollowRedirection = false; - - /** - * Helper: FunctionalTest is only able to follow redirection once, we want to go all the way. - */ - function getAndFollowAll($url) { - $response = $this->get($url); - while ($location = $response->getHeader('Location')) { - $response = $this->mainSession->followRedirection(); - } - echo $response->getHeader('Location'); - - return $response; - } - - /** - * Anonymous user cannot access anything. - */ - function testAnonymousIsForbiddenAdminAccess() { - $response = $this->getAndFollowAll('admin/pages/?SubsiteID=0'); - $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed'); - - $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); - $response = $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); - $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed'); - - $response = $this->getAndFollowAll(SubsiteXHRController::class); - $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), - 'SubsiteXHRController is disallowed'); - } - - /** - * Admin should be able to access all subsites and the main site - */ - function testAdminCanAccessAllSubsites() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); - Session::set("loggedInAs", $member->ID); - - $this->getAndFollowAll('admin/pages/?SubsiteID=0'); - $this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.'); - $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); - - $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); - $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.'); - $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); - - $response = $this->getAndFollowAll(SubsiteXHRController::class); - $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), - 'SubsiteXHRController is reachable'); - } - - function testAdminIsRedirectedToObjectsSubsite() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); - Session::set("loggedInAs", $member->ID); - - $mainSubsitePage = $this->objFromFixture('Page', 'mainSubsitePage'); - $subsite1Home = $this->objFromFixture('Page', 'subsite1_home'); - - Config::inst()->nest(); - - Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', false); - Subsite::changeSubsite(0); - $this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, 'Loading an object switches the subsite'); - $this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section'); - - Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', true); - Subsite::changeSubsite(0); - $this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, 'Loading a non-main-site object still switches the subsite if configured with treats_subsite_0_as_global'); - $this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section'); - - $this->getAndFollowAll("admin/pages/edit/show/$mainSubsitePage->ID"); - $this->assertNotEquals(Subsite::currentSubsiteID(), $mainSubsitePage->SubsiteID, 'Loading a main-site object does not change the subsite if configured with treats_subsite_0_as_global'); - $this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section'); - - Config::inst()->unnest(); - } - - /** - * User which has AccessAllSubsites set to 1 should be able to access all subsites and main site, - * even though he does not have the ADMIN permission. - */ - function testEditorCanAccessAllSubsites() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); - Session::set("loggedInAs", $member->ID); - - $this->getAndFollowAll('admin/pages/?SubsiteID=0'); - $this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.'); - $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); - - $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); - $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.'); - $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); - - $response = $this->getAndFollowAll(SubsiteXHRController::class); - $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), - 'SubsiteXHRController is reachable'); - } - - /** - * Test a member who only has access to one subsite (subsite1) and only some sections (pages and security). - */ - function testSubsiteAdmin() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); - Session::set("loggedInAs", $member->ID); - - $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); - - // Check allowed URL. - $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access own subsite.'); - $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Can access permitted section.'); - - // Check forbidden section in allowed subsite. - $this->getAndFollowAll("admin/assets/?SubsiteID={$subsite1->ID}"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected within subsite.'); - $this->assertNotRegExp('#^admin/assets/.*#', $this->mainSession->lastUrl(), - 'Is redirected away from forbidden section'); - - // Check forbidden site, on a section that's allowed on another subsite - $this->getAndFollowAll("admin/pages/?SubsiteID=0"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to permitted subsite.'); - - // Check forbidden site, on a section that's not allowed on any other subsite - $this->getAndFollowAll("admin/assets/?SubsiteID=0"); - $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to first permitted subsite.'); - $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Is not denied access'); - - // Check the standalone XHR controller. - $response = $this->getAndFollowAll(SubsiteXHRController::class); - $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), - 'SubsiteXHRController is reachable'); - } +class SubsiteAdminFunctionalTest extends FunctionalTest +{ + static $fixture_file = 'subsites/tests/SubsiteTest.yml'; + static $use_draft_site = true; + + protected $autoFollowRedirection = false; + + /** + * Helper: FunctionalTest is only able to follow redirection once, we want to go all the way. + */ + function getAndFollowAll($url) + { + $response = $this->get($url); + while ($location = $response->getHeader('Location')) { + $response = $this->mainSession->followRedirection(); + } + echo $response->getHeader('Location'); + + return $response; + } + + /** + * Anonymous user cannot access anything. + */ + function testAnonymousIsForbiddenAdminAccess() + { + $response = $this->getAndFollowAll('admin/pages/?SubsiteID=0'); + $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed'); + + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + $response = $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); + $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed'); + + $response = $this->getAndFollowAll(SubsiteXHRController::class); + $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), + 'SubsiteXHRController is disallowed'); + } + + /** + * Admin should be able to access all subsites and the main site + */ + function testAdminCanAccessAllSubsites() + { + $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); + Session::set("loggedInAs", $member->ID); + + $this->getAndFollowAll('admin/pages/?SubsiteID=0'); + $this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.'); + $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); + + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.'); + $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); + + $response = $this->getAndFollowAll(SubsiteXHRController::class); + $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), + 'SubsiteXHRController is reachable'); + } + + function testAdminIsRedirectedToObjectsSubsite() + { + $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); + Session::set("loggedInAs", $member->ID); + + $mainSubsitePage = $this->objFromFixture('Page', 'mainSubsitePage'); + $subsite1Home = $this->objFromFixture('Page', 'subsite1_home'); + + Config::inst()->nest(); + + Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', + false); + Subsite::changeSubsite(0); + $this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, + 'Loading an object switches the subsite'); + $this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section'); + + Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', + true); + Subsite::changeSubsite(0); + $this->getAndFollowAll("admin/pages/edit/show/$subsite1Home->ID"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1Home->SubsiteID, + 'Loading a non-main-site object still switches the subsite if configured with treats_subsite_0_as_global'); + $this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section'); + + $this->getAndFollowAll("admin/pages/edit/show/$mainSubsitePage->ID"); + $this->assertNotEquals(Subsite::currentSubsiteID(), $mainSubsitePage->SubsiteID, + 'Loading a main-site object does not change the subsite if configured with treats_subsite_0_as_global'); + $this->assertRegExp("#^admin/pages.*#", $this->mainSession->lastUrl(), 'Lands on the correct section'); + + Config::inst()->unnest(); + } + + /** + * User which has AccessAllSubsites set to 1 should be able to access all subsites and main site, + * even though he does not have the ADMIN permission. + */ + function testEditorCanAccessAllSubsites() + { + $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); + Session::set("loggedInAs", $member->ID); + + $this->getAndFollowAll('admin/pages/?SubsiteID=0'); + $this->assertEquals(Subsite::currentSubsiteID(), '0', 'Can access main site.'); + $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); + + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access other subsite.'); + $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Lands on the correct section'); + + $response = $this->getAndFollowAll(SubsiteXHRController::class); + $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), + 'SubsiteXHRController is reachable'); + } + + /** + * Test a member who only has access to one subsite (subsite1) and only some sections (pages and security). + */ + function testSubsiteAdmin() + { + $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + Session::set("loggedInAs", $member->ID); + + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + + // Check allowed URL. + $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Can access own subsite.'); + $this->assertRegExp('#^admin/pages.*#', $this->mainSession->lastUrl(), 'Can access permitted section.'); + + // Check forbidden section in allowed subsite. + $this->getAndFollowAll("admin/assets/?SubsiteID={$subsite1->ID}"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected within subsite.'); + $this->assertNotRegExp('#^admin/assets/.*#', $this->mainSession->lastUrl(), + 'Is redirected away from forbidden section'); + + // Check forbidden site, on a section that's allowed on another subsite + $this->getAndFollowAll("admin/pages/?SubsiteID=0"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to permitted subsite.'); + + // Check forbidden site, on a section that's not allowed on any other subsite + $this->getAndFollowAll("admin/assets/?SubsiteID=0"); + $this->assertEquals(Subsite::currentSubsiteID(), $subsite1->ID, 'Is redirected to first permitted subsite.'); + $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Is not denied access'); + + // Check the standalone XHR controller. + $response = $this->getAndFollowAll(SubsiteXHRController::class); + $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), + 'SubsiteXHRController is reachable'); + } } diff --git a/tests/SubsiteAdminTest.php b/tests/SubsiteAdminTest.php index 9facc299..28c46043 100644 --- a/tests/SubsiteAdminTest.php +++ b/tests/SubsiteAdminTest.php @@ -1,56 +1,64 @@ $this->idFromFixture('SilverStripe\\Security\\Member', 'admin') - )); + ]); } /** * Test generation of the view */ - function testBasicView() { - Subsite::$write_hostmap = false; - $subsite1ID = $this->objFromFixture(Subsite::class,'domaintest1')->ID; + function testBasicView() + { + Subsite::$write_hostmap = false; + $subsite1ID = $this->objFromFixture(Subsite::class, 'domaintest1')->ID; // Open the admin area logged in as admin $response1 = Director::test('admin/subsites/', null, $this->adminLoggedInSession()); - + // Confirm that this URL gets you the entire page, with the edit form loaded - $response2 = Director::test("admin/subsites/Subsite/EditForm/field/Subsite/item/$subsite1ID/edit", null, $this->adminLoggedInSession()); - $this->assertTrue(strpos($response2->getBody(), 'id="Form_ItemEditForm_ID"') !== false, "Testing Form_ItemEditForm_ID exists"); + $response2 = Director::test("admin/subsites/Subsite/EditForm/field/Subsite/item/$subsite1ID/edit", null, + $this->adminLoggedInSession()); + $this->assertTrue(strpos($response2->getBody(), 'id="Form_ItemEditForm_ID"') !== false, + "Testing Form_ItemEditForm_ID exists"); $this->assertTrue(strpos($response2->getBody(), ' exists"); } - - /** - * 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('SilverStripe\\Security\\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::class,'main'), $ids, "Site with no groups inaccesible"); - $this->assertArrayHasKey($this->idFromFixture(Subsite::class,'subsite1'), $ids, "Subsite1 Template inaccessible"); - $this->assertArrayHasKey($this->idFromFixture(Subsite::class,'subsite2'), $ids, "Subsite2 Template inaccessible"); - } - - + + /** + * 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('SilverStripe\\Security\\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::class, 'main'), $ids, "Site with no groups inaccesible"); + $this->assertArrayHasKey($this->idFromFixture(Subsite::class, 'subsite1'), $ids, + "Subsite1 Template inaccessible"); + $this->assertArrayHasKey($this->idFromFixture(Subsite::class, 'subsite2'), $ids, + "Subsite2 Template inaccessible"); + } + + } diff --git a/tests/SubsiteTest.php b/tests/SubsiteTest.php index 21dd23bf..348452d0 100644 --- a/tests/SubsiteTest.php +++ b/tests/SubsiteTest.php @@ -1,368 +1,390 @@ origStrictSubdomainMatching = Subsite::$strict_subdomain_matching; - Subsite::$strict_subdomain_matching = false; - } - - function tearDown() { - parent::tearDown(); - - Subsite::$strict_subdomain_matching = $this->origStrictSubdomainMatching; - } - - /** - * Create a new subsite from the template and verify that all the template's pages are copied - */ - function testSubsiteCreation() { - Subsite::$write_hostmap = false; - - // Create the instance - $template = $this->objFromFixture(Subsite::class, 'main'); - - // Test that changeSubsite is working - Subsite::changeSubsite($template->ID); - $tmplStaff = $this->objFromFixture('Page','staff'); - $tmplHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'"); - - // Publish all the pages in the template, testing that DataObject::get only returns pages from the chosen subsite - $pages = DataObject::get("SilverStripe\\CMS\\Model\\SiteTree"); - $totalPages = $pages->Count(); - foreach($pages as $page) { - $this->assertEquals($template->ID, $page->SubsiteID); - $page->publish('Stage', 'Live'); - } - - // Create a new site - $subsite = $template->duplicate(); - - // Check title - $this->assertEquals($subsite->Title, $template->Title); - - // Another test that changeSubsite is working - $subsite->activate(); - - $siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'"); - $this->assertNotEquals($siteHome, false, 'Home Page for subsite not found'); - $this->assertEquals($subsite->ID, $siteHome->SubsiteID, - 'createInstance() copies existing pages retaining the same URLSegment' - ); - - Subsite::changeSubsite(0); - } - - /** - * Confirm that domain lookup is working - */ - function testDomainLookup() { - // Clear existing fixtures - foreach(DataObject::get(Subsite::class) as $subsite) $subsite->delete(); - foreach(DataObject::get(SubsiteDomain::class) as $domain) $domain->delete(); - - // Much more expressive than YML in this case - $subsite1 = $this->createSubsiteWithDomains(array( - 'one.example.org' => true, - 'one.*' => false, - )); - $subsite2 = $this->createSubsiteWithDomains(array( - 'two.mysite.com' => true, - '*.mysite.com' => false, - 'subdomain.onmultiplesubsites.com' => false, - )); - $subsite3 = $this->createSubsiteWithDomains(array( - 'three.*' => true, // wildcards in primary domain are not recommended - 'subdomain.unique.com' => false, - '*.onmultiplesubsites.com' => false, - )); - - $this->assertEquals( - $subsite3->ID, - Subsite::getSubsiteIDForDomain('subdomain.unique.com'), - 'Full unique match' - ); - - $this->assertEquals( - $subsite1->ID, - Subsite::getSubsiteIDForDomain('one.example.org'), - 'Full match, doesn\'t complain about multiple matches within a single subsite' - ); - - $failed = false; - try { - Subsite::getSubsiteIDForDomain('subdomain.onmultiplesubsites.com'); - } catch(UnexpectedValueException $e) { - $failed = true; - } - $this->assertTrue( - $failed, - 'Fails on multiple matches with wildcard vs. www across multiple subsites' - ); - - $this->assertEquals( - $subsite1->ID, - Subsite::getSubsiteIDForDomain('one.unique.com'), - 'Fuzzy match suffixed with wildcard (rule "one.*")' - ); - - $this->assertEquals( - $subsite2->ID, - Subsite::getSubsiteIDForDomain('two.mysite.com'), - 'Matches correct subsite for rule' - ); - - $this->assertEquals( - $subsite2->ID, - Subsite::getSubsiteIDForDomain('other.mysite.com'), - 'Fuzzy match prefixed with wildcard (rule "*.mysite.com")' - ); - - $this->assertEquals( - 0, - Subsite::getSubsiteIDForDomain('unknown.madeup.com'), - "Doesn't match unknown subsite" - ); - - } - - function testStrictSubdomainMatching() { - // Clear existing fixtures - foreach(DataObject::get(Subsite::class) as $subsite) $subsite->delete(); - foreach(DataObject::get(SubsiteDomain::class) as $domain) $domain->delete(); - - // Much more expressive than YML in this case - $subsite1 = $this->createSubsiteWithDomains(array( - 'example.org' => true, - 'example.com' => false, - '*.wildcard.com' => false, - )); - $subsite2 = $this->createSubsiteWithDomains(array( - 'www.example.org' => true, - 'www.wildcard.com' => false, - )); - - Subsite::$strict_subdomain_matching = false; - - $this->assertEquals( - $subsite1->ID, - Subsite::getSubsiteIDForDomain('example.org'), - 'Exact matches without strict checking when not using www prefix' - ); - $this->assertEquals( - $subsite1->ID, - Subsite::getSubsiteIDForDomain('www.example.org'), - 'Matches without strict checking when using www prefix, still matching first domain regardless of www prefix (falling back to subsite primary key ordering)' - ); - $this->assertEquals( - $subsite1->ID, - Subsite::getSubsiteIDForDomain('www.example.com'), - 'Fuzzy matches without strict checking with www prefix' - ); - $this->assertEquals( - 0, - Subsite::getSubsiteIDForDomain('www.wildcard.com'), - 'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place' - ); - - Subsite::$strict_subdomain_matching = true; - - $this->assertEquals( - $subsite1->ID, - Subsite::getSubsiteIDForDomain('example.org'), - 'Matches with strict checking when not using www prefix' - ); - $this->assertEquals( - $subsite2->ID, // not 1 - Subsite::getSubsiteIDForDomain('www.example.org'), - 'Matches with strict checking when using www prefix' - ); - $this->assertEquals( - 0, - Subsite::getSubsiteIDForDomain('www.example.com'), - 'Doesn\'t fuzzy match with strict checking when using www prefix' - ); - $failed = false; - try { - Subsite::getSubsiteIDForDomain('www.wildcard.com'); - } catch(UnexpectedValueException $e) { - $failed = true; - } - $this->assertTrue( - $failed, - 'Fails on multiple matches with strict checking and wildcard vs. www' - ); - - } - - protected function createSubsiteWithDomains($domains) { - $subsite = new Subsite(array( - 'Title' => 'My Subsite' - )); - $subsite->write(); - foreach($domains as $domainStr => $isPrimary) { - $domain = new SubsiteDomain(array( - 'Domain' => $domainStr, - 'IsPrimary' => $isPrimary, - 'SubsiteID' => $subsite->ID - )); - $domain->write(); - } - - return $subsite; - } - - /** - * Test the Subsite->domain() method - */ - function testDefaultDomain() { - $this->assertEquals('one.example.org', - $this->objFromFixture(Subsite::class,'domaintest1')->domain()); - - $this->assertEquals('two.mysite.com', - $this->objFromFixture(Subsite::class,'domaintest2')->domain()); - - $originalHTTPHost = $_SERVER['HTTP_HOST']; - - $_SERVER['HTTP_HOST'] = "www.example.org"; - $this->assertEquals('three.example.org', - $this->objFromFixture(Subsite::class,'domaintest3')->domain()); - - $_SERVER['HTTP_HOST'] = "mysite.example.org"; - $this->assertEquals('three.mysite.example.org', - $this->objFromFixture(Subsite::class,'domaintest3')->domain()); - - $this->assertEquals($_SERVER['HTTP_HOST'], singleton(Subsite::class)->PrimaryDomain); - $this->assertEquals('http://'.$_SERVER['HTTP_HOST'].Director::baseURL(), singleton(Subsite::class)->absoluteBaseURL()); - - $_SERVER['HTTP_HOST'] = $originalHTTPHost; - } - - function testAllSites() { - $subsites = Subsite::all_sites(); - $this->assertDOSEquals(array( - array('Title' =>'Main site'), - array('Title' =>'Template'), - array('Title' =>'Subsite1 Template'), - array('Title' =>'Subsite2 Template'), - array('Title' =>'Test 1'), - array('Title' =>'Test 2'), - array('Title' =>'Test 3') - ), $subsites, 'Lists all subsites'); - } - - function testAllAccessibleSites() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); - - $subsites = Subsite::all_accessible_sites(true, 'Main site', $member); - $this->assertDOSEquals(array( - array('Title' =>'Subsite1 Template') - ), $subsites, 'Lists member-accessible sites.'); - } - - /** - * Test Subsite::accessible_sites() - */ - function testAccessibleSites() { - $member1Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, - $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member')); - $member1SiteTitles = $member1Sites->column("Title"); - sort($member1SiteTitles); - $this->assertEquals('Subsite1 Template', $member1SiteTitles[0], 'Member can get to a subsite via a group'); - - $adminSites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, - $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')); - $adminSiteTitles = $adminSites->column("Title"); - sort($adminSiteTitles); - $this->assertEquals(array( - 'Subsite1 Template', - 'Subsite2 Template', - 'Template', - 'Test 1', - 'Test 2', - 'Test 3', - ), $adminSiteTitles); - - $member2Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, - $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member2')); - $member2SiteTitles = $member2Sites->column("Title"); - sort($member2SiteTitles); - $this->assertEquals('Subsite1 Template', $member2SiteTitles[0], 'Member can get to subsite via a group role'); - } - - function testhasMainSitePermission() { - $admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); - $subsite1member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); - $subsite1admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1admin'); - $allsubsitesauthor = $this->objFromFixture('SilverStripe\\Security\\Member', 'allsubsitesauthor'); - - $this->assertTrue( - Subsite::hasMainSitePermission($admin), - 'Default permissions granted for super-admin' - ); - $this->assertTrue( - Subsite::hasMainSitePermission($admin, array("ADMIN")), - 'ADMIN permissions granted for super-admin' - ); - $this->assertFalse( - Subsite::hasMainSitePermission($subsite1admin, array("ADMIN")), - 'ADMIN permissions (on main site) denied for subsite1 admin' - ); - $this->assertFalse( - Subsite::hasMainSitePermission($subsite1admin, array("CMS_ACCESS_CMSMain")), - 'CMS_ACCESS_CMSMain (on main site) denied for subsite1 admin' - ); - $this->assertFalse( - Subsite::hasMainSitePermission($allsubsitesauthor, array("ADMIN")), - 'ADMIN permissions (on main site) denied for CMS author with edit rights on all subsites' - ); - $this->assertTrue( - Subsite::hasMainSitePermission($allsubsitesauthor, array("CMS_ACCESS_CMSMain")), - 'CMS_ACCESS_CMSMain (on main site) granted for CMS author with edit rights on all subsites' - ); - $this->assertFalse( - Subsite::hasMainSitePermission($subsite1member, array("ADMIN")), - 'ADMIN (on main site) denied for subsite1 subsite1 cms author' - ); - $this->assertFalse( - Subsite::hasMainSitePermission($subsite1member, array("CMS_ACCESS_CMSMain")), - 'CMS_ACCESS_CMSMain (on main site) denied for subsite1 cms author' - ); - } - - function testDuplicateSubsite() { - // get subsite1 & create page - $subsite1 = $this->objFromFixture(Subsite::class,'domaintest1'); - $subsite1->activate(); - $page1 = new Page(); - $page1->Title = 'MyAwesomePage'; - $page1->write(); - $page1->doPublish(); - $this->assertEquals($page1->SubsiteID, $subsite1->ID); - - // duplicate - $subsite2 = $subsite1->duplicate(); - $subsite2->activate(); - // change content on dupe - $page2 = DataObject::get_one('Page', "\"Title\" = 'MyAwesomePage'"); - $page2->Title = 'MyNewAwesomePage'; - $page2->write(); - $page2->doPublish(); - - // check change & check change has not affected subiste1 - $subsite1->activate(); - $this->assertEquals('MyAwesomePage', DataObject::get_by_id('Page', $page1->ID)->Title); - $subsite2->activate(); - $this->assertEquals('MyNewAwesomePage', DataObject::get_by_id('Page', $page2->ID)->Title); - } +class SubsiteTest extends BaseSubsiteTest +{ + + static $fixture_file = 'subsites/tests/SubsiteTest.yml'; + + function setUp() + { + parent::setUp(); + + $this->origStrictSubdomainMatching = Subsite::$strict_subdomain_matching; + Subsite::$strict_subdomain_matching = false; + } + + function tearDown() + { + parent::tearDown(); + + Subsite::$strict_subdomain_matching = $this->origStrictSubdomainMatching; + } + + /** + * Create a new subsite from the template and verify that all the template's pages are copied + */ + function testSubsiteCreation() + { + Subsite::$write_hostmap = false; + + // Create the instance + $template = $this->objFromFixture(Subsite::class, 'main'); + + // Test that changeSubsite is working + Subsite::changeSubsite($template->ID); + $tmplStaff = $this->objFromFixture('Page', 'staff'); + $tmplHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'"); + + // Publish all the pages in the template, testing that DataObject::get only returns pages from the chosen subsite + $pages = DataObject::get("SilverStripe\\CMS\\Model\\SiteTree"); + $totalPages = $pages->Count(); + foreach ($pages as $page) { + $this->assertEquals($template->ID, $page->SubsiteID); + $page->publish('Stage', 'Live'); + } + + // Create a new site + $subsite = $template->duplicate(); + + // Check title + $this->assertEquals($subsite->Title, $template->Title); + + // Another test that changeSubsite is working + $subsite->activate(); + + $siteHome = DataObject::get_one('Page', "\"URLSegment\" = 'home'"); + $this->assertNotEquals($siteHome, false, 'Home Page for subsite not found'); + $this->assertEquals($subsite->ID, $siteHome->SubsiteID, + 'createInstance() copies existing pages retaining the same URLSegment' + ); + + Subsite::changeSubsite(0); + } + + /** + * Confirm that domain lookup is working + */ + function testDomainLookup() + { + // Clear existing fixtures + foreach (DataObject::get(Subsite::class) as $subsite) { + $subsite->delete(); + } + foreach (DataObject::get(SubsiteDomain::class) as $domain) { + $domain->delete(); + } + + // Much more expressive than YML in this case + $subsite1 = $this->createSubsiteWithDomains([ + 'one.example.org' => true, + 'one.*' => false, + ]); + $subsite2 = $this->createSubsiteWithDomains([ + 'two.mysite.com' => true, + '*.mysite.com' => false, + 'subdomain.onmultiplesubsites.com' => false, + ]); + $subsite3 = $this->createSubsiteWithDomains([ + 'three.*' => true, // wildcards in primary domain are not recommended + 'subdomain.unique.com' => false, + '*.onmultiplesubsites.com' => false, + ]); + + $this->assertEquals( + $subsite3->ID, + Subsite::getSubsiteIDForDomain('subdomain.unique.com'), + 'Full unique match' + ); + + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('one.example.org'), + 'Full match, doesn\'t complain about multiple matches within a single subsite' + ); + + $failed = false; + try { + Subsite::getSubsiteIDForDomain('subdomain.onmultiplesubsites.com'); + } catch (UnexpectedValueException $e) { + $failed = true; + } + $this->assertTrue( + $failed, + 'Fails on multiple matches with wildcard vs. www across multiple subsites' + ); + + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('one.unique.com'), + 'Fuzzy match suffixed with wildcard (rule "one.*")' + ); + + $this->assertEquals( + $subsite2->ID, + Subsite::getSubsiteIDForDomain('two.mysite.com'), + 'Matches correct subsite for rule' + ); + + $this->assertEquals( + $subsite2->ID, + Subsite::getSubsiteIDForDomain('other.mysite.com'), + 'Fuzzy match prefixed with wildcard (rule "*.mysite.com")' + ); + + $this->assertEquals( + 0, + Subsite::getSubsiteIDForDomain('unknown.madeup.com'), + "Doesn't match unknown subsite" + ); + + } + + function testStrictSubdomainMatching() + { + // Clear existing fixtures + foreach (DataObject::get(Subsite::class) as $subsite) { + $subsite->delete(); + } + foreach (DataObject::get(SubsiteDomain::class) as $domain) { + $domain->delete(); + } + + // Much more expressive than YML in this case + $subsite1 = $this->createSubsiteWithDomains([ + 'example.org' => true, + 'example.com' => false, + '*.wildcard.com' => false, + ]); + $subsite2 = $this->createSubsiteWithDomains([ + 'www.example.org' => true, + 'www.wildcard.com' => false, + ]); + + Subsite::$strict_subdomain_matching = false; + + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('example.org'), + 'Exact matches without strict checking when not using www prefix' + ); + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('www.example.org'), + 'Matches without strict checking when using www prefix, still matching first domain regardless of www prefix (falling back to subsite primary key ordering)' + ); + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('www.example.com'), + 'Fuzzy matches without strict checking with www prefix' + ); + $this->assertEquals( + 0, + Subsite::getSubsiteIDForDomain('www.wildcard.com'), + 'Doesn\'t match www prefix without strict check, even if a wildcard subdomain is in place' + ); + + Subsite::$strict_subdomain_matching = true; + + $this->assertEquals( + $subsite1->ID, + Subsite::getSubsiteIDForDomain('example.org'), + 'Matches with strict checking when not using www prefix' + ); + $this->assertEquals( + $subsite2->ID, // not 1 + Subsite::getSubsiteIDForDomain('www.example.org'), + 'Matches with strict checking when using www prefix' + ); + $this->assertEquals( + 0, + Subsite::getSubsiteIDForDomain('www.example.com'), + 'Doesn\'t fuzzy match with strict checking when using www prefix' + ); + $failed = false; + try { + Subsite::getSubsiteIDForDomain('www.wildcard.com'); + } catch (UnexpectedValueException $e) { + $failed = true; + } + $this->assertTrue( + $failed, + 'Fails on multiple matches with strict checking and wildcard vs. www' + ); + + } + + protected function createSubsiteWithDomains($domains) + { + $subsite = new Subsite([ + 'Title' => 'My Subsite' + ]); + $subsite->write(); + foreach ($domains as $domainStr => $isPrimary) { + $domain = new SubsiteDomain([ + 'Domain' => $domainStr, + 'IsPrimary' => $isPrimary, + 'SubsiteID' => $subsite->ID + ]); + $domain->write(); + } + + return $subsite; + } + + /** + * Test the Subsite->domain() method + */ + function testDefaultDomain() + { + $this->assertEquals('one.example.org', + $this->objFromFixture(Subsite::class, 'domaintest1')->domain()); + + $this->assertEquals('two.mysite.com', + $this->objFromFixture(Subsite::class, 'domaintest2')->domain()); + + $originalHTTPHost = $_SERVER['HTTP_HOST']; + + $_SERVER['HTTP_HOST'] = "www.example.org"; + $this->assertEquals('three.example.org', + $this->objFromFixture(Subsite::class, 'domaintest3')->domain()); + + $_SERVER['HTTP_HOST'] = "mysite.example.org"; + $this->assertEquals('three.mysite.example.org', + $this->objFromFixture(Subsite::class, 'domaintest3')->domain()); + + $this->assertEquals($_SERVER['HTTP_HOST'], singleton(Subsite::class)->PrimaryDomain); + $this->assertEquals('http://' . $_SERVER['HTTP_HOST'] . Director::baseURL(), + singleton(Subsite::class)->absoluteBaseURL()); + + $_SERVER['HTTP_HOST'] = $originalHTTPHost; + } + + function testAllSites() + { + $subsites = Subsite::all_sites(); + $this->assertDOSEquals([ + ['Title' => 'Main site'], + ['Title' => 'Template'], + ['Title' => 'Subsite1 Template'], + ['Title' => 'Subsite2 Template'], + ['Title' => 'Test 1'], + ['Title' => 'Test 2'], + ['Title' => 'Test 3'] + ], $subsites, 'Lists all subsites'); + } + + function testAllAccessibleSites() + { + $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + + $subsites = Subsite::all_accessible_sites(true, 'Main site', $member); + $this->assertDOSEquals([ + ['Title' => 'Subsite1 Template'] + ], $subsites, 'Lists member-accessible sites.'); + } + + /** + * Test Subsite::accessible_sites() + */ + function testAccessibleSites() + { + $member1Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, + $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member')); + $member1SiteTitles = $member1Sites->column("Title"); + sort($member1SiteTitles); + $this->assertEquals('Subsite1 Template', $member1SiteTitles[0], 'Member can get to a subsite via a group'); + + $adminSites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, + $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')); + $adminSiteTitles = $adminSites->column("Title"); + sort($adminSiteTitles); + $this->assertEquals([ + 'Subsite1 Template', + 'Subsite2 Template', + 'Template', + 'Test 1', + 'Test 2', + 'Test 3', + ], $adminSiteTitles); + + $member2Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, + $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member2')); + $member2SiteTitles = $member2Sites->column("Title"); + sort($member2SiteTitles); + $this->assertEquals('Subsite1 Template', $member2SiteTitles[0], 'Member can get to subsite via a group role'); + } + + function testhasMainSitePermission() + { + $admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); + $subsite1member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + $subsite1admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1admin'); + $allsubsitesauthor = $this->objFromFixture('SilverStripe\\Security\\Member', 'allsubsitesauthor'); + + $this->assertTrue( + Subsite::hasMainSitePermission($admin), + 'Default permissions granted for super-admin' + ); + $this->assertTrue( + Subsite::hasMainSitePermission($admin, ["ADMIN"]), + 'ADMIN permissions granted for super-admin' + ); + $this->assertFalse( + Subsite::hasMainSitePermission($subsite1admin, ["ADMIN"]), + 'ADMIN permissions (on main site) denied for subsite1 admin' + ); + $this->assertFalse( + Subsite::hasMainSitePermission($subsite1admin, ["CMS_ACCESS_CMSMain"]), + 'CMS_ACCESS_CMSMain (on main site) denied for subsite1 admin' + ); + $this->assertFalse( + Subsite::hasMainSitePermission($allsubsitesauthor, ["ADMIN"]), + 'ADMIN permissions (on main site) denied for CMS author with edit rights on all subsites' + ); + $this->assertTrue( + Subsite::hasMainSitePermission($allsubsitesauthor, ["CMS_ACCESS_CMSMain"]), + 'CMS_ACCESS_CMSMain (on main site) granted for CMS author with edit rights on all subsites' + ); + $this->assertFalse( + Subsite::hasMainSitePermission($subsite1member, ["ADMIN"]), + 'ADMIN (on main site) denied for subsite1 subsite1 cms author' + ); + $this->assertFalse( + Subsite::hasMainSitePermission($subsite1member, ["CMS_ACCESS_CMSMain"]), + 'CMS_ACCESS_CMSMain (on main site) denied for subsite1 cms author' + ); + } + + function testDuplicateSubsite() + { + // get subsite1 & create page + $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); + $subsite1->activate(); + $page1 = new Page(); + $page1->Title = 'MyAwesomePage'; + $page1->write(); + $page1->doPublish(); + $this->assertEquals($page1->SubsiteID, $subsite1->ID); + + // duplicate + $subsite2 = $subsite1->duplicate(); + $subsite2->activate(); + // change content on dupe + $page2 = DataObject::get_one('Page', "\"Title\" = 'MyAwesomePage'"); + $page2->Title = 'MyNewAwesomePage'; + $page2->write(); + $page2->doPublish(); + + // check change & check change has not affected subiste1 + $subsite1->activate(); + $this->assertEquals('MyAwesomePage', DataObject::get_by_id('Page', $page1->ID)->Title); + $subsite2->activate(); + $this->assertEquals('MyNewAwesomePage', DataObject::get_by_id('Page', $page2->ID)->Title); + } } diff --git a/tests/SubsitesVirtualPageTest.php b/tests/SubsitesVirtualPageTest.php index 52f9c5bf..dda8a87d 100644 --- a/tests/SubsitesVirtualPageTest.php +++ b/tests/SubsitesVirtualPageTest.php @@ -1,279 +1,289 @@ objFromFixture('SilverStripe\\Assets\\File', 'file1'); - $page = $this->objFromFixture('SilverStripe\\CMS\\Model\\SiteTree', 'page1'); - $fromPath = __DIR__ . '/testscript-test-file.pdf'; - $destPath = TestAssetStore::getLocalPath($file); - Filesystem::makeFolder(dirname($destPath)); - copy($fromPath, $destPath); - - // Hack in site link tracking after the fact - $page->Content = '

'; - $page->write(); - } - - public function tearDown() { - TestAssetStore::reset(); - parent::tearDown(); - } - - // Attempt to bring main:linky to subsite2:linky - function testVirtualPageFromAnotherSubsite() { - Subsite::$write_hostmap = false; - - $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); - - Subsite::changeSubsite($subsite->ID); - Subsite::$disable_subsite_filter = false; - - $linky = $this->objFromFixture('Page', 'linky'); - - $svp = new SubsitesVirtualPage(); - $svp->CopyContentFromID = $linky->ID; - $svp->SubsiteID = $subsite->ID; - $svp->URLSegment = 'linky'; - - $svp->write(); - - $this->assertEquals($svp->SubsiteID, $subsite->ID); - $this->assertEquals($svp->Title, $linky->Title); - } - - function testFileLinkRewritingOnVirtualPages() { - // File setup - $this->logInWithPermission('ADMIN'); - - // Publish the source page - $page = $this->objFromFixture('SilverStripe\\CMS\\Model\\SiteTree', 'page1'); - $this->assertTrue($page->doPublish()); - - // Create a virtual page from it, and publish that - $svp = new SubsitesVirtualPage(); - $svp->CopyContentFromID = $page->ID; - $svp->write(); - $svp->doPublish(); - - // Rename the file - $file = $this->objFromFixture('SilverStripe\\Assets\\File', 'file1'); - $file->Name = 'renamed-test-file.pdf'; - $file->write(); - - // Verify that the draft and publish virtual pages both have the corrected link - $this->assertContains('ID")->value()); - $this->assertContains('ID")->value()); - } - - function testSubsiteVirtualPagesArentInappropriatelyPublished() { - // Fixture - $p = new Page(); - $p->Content = "test content"; - $p->write(); - $vp = new SubsitesVirtualPage(); - $vp->CopyContentFromID = $p->ID; - $vp->write(); - - // VP is oragne - $this->assertTrue($vp->IsAddedToStage); - - // VP is still orange after we publish - $p->doPublish(); - $this->fixVersionNumberCache($vp); - $this->assertTrue($vp->IsAddedToStage); - - // A new VP created after P's initial construction - $vp2 = new SubsitesVirtualPage(); - $vp2->CopyContentFromID = $p->ID; - $vp2->write(); - $this->assertTrue($vp2->IsAddedToStage); - - // Also remains orange after a republish - $p->Content = "new content"; - $p->write(); - $p->doPublish(); - $this->fixVersionNumberCache($vp2); - $this->assertTrue($vp2->IsAddedToStage); - - // VP is now published - $vp->doPublish(); - - $this->fixVersionNumberCache($vp); - $this->assertTrue($vp->ExistsOnLive); - $this->assertFalse($vp->IsModifiedOnStage); - - // P edited, VP and P both go green - $p->Content = "third content"; - $p->write(); - - $this->fixVersionNumberCache($vp, $p); - $this->assertTrue($p->IsModifiedOnStage); - $this->assertTrue($vp->IsModifiedOnStage); - - // Publish, VP goes black - $p->doPublish(); - $this->fixVersionNumberCache($vp); - $this->assertTrue($vp->ExistsOnLive); - $this->assertFalse($vp->IsModifiedOnStage); - } - - /** - * This test ensures published Subsites Virtual Pages immediately reflect updates - * to their published target pages. Note - this has to happen when the virtual page - * is in a different subsite to the page you are editing and republishing, - * otherwise the test will pass falsely due to current subsite ID being the same. - */ - function testPublishedSubsiteVirtualPagesUpdateIfTargetPageUpdates() - { - // create page - $p = new Page(); - $p->Content = 'Content'; - $p->Title = 'Title'; - $p->writeToStage('Stage'); - $p->publish('Stage', 'Live'); - $this->assertTrue($p->ExistsOnLive); - - // change to subsite - $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); - Subsite::changeSubsite($subsite->ID); - Subsite::$disable_subsite_filter = false; - - // create svp in subsite - $svp = new SubsitesVirtualPage(); - $svp->CopyContentFromID = $p->ID; - $svp->write(); - $svp->writeToStage('Stage'); - $svp->publish('Stage', 'Live'); - $this->assertEquals($svp->SubsiteID, $subsite->ID); - $this->assertTrue($svp->ExistsOnLive); - - // change back to original subsite ("Main site") - Subsite::changeSubsite(0); - - // update original page - $p->Title = 'New Title'; - // "save & publish" - $p->writeToStage('Stage'); - $p->publish('Stage', 'Live'); - $this->assertNotEquals($p->SubsiteID, $subsite->ID); - - // reload SVP from database - // can't use DO::get by id because caches. - $svpdb = $svp->get()->byID($svp->ID); - - // ensure title changed - $this->assertEquals($svpdb->Title, $p->Title); - } - - function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() { - Config::modify()->set('StaticPublisher', 'disable_realtime', true); - - // Go to main site, get parent page - $subsite = $this->objFromFixture(Subsite::class, 'main'); - Subsite::changeSubsite($subsite->ID); - $page = $this->objFromFixture('Page', 'importantpage'); - - // Create two SVPs on other subsites - $subsite = $this->objFromFixture(Subsite::class, 'subsite1'); - Subsite::changeSubsite($subsite->ID); - $vp1 = new SubsitesVirtualPage(); - $vp1->CopyContentFromID = $page->ID; - $vp1->write(); - $vp1->doPublish(); - - $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); - Subsite::changeSubsite($subsite->ID); - $vp2 = new SubsitesVirtualPage(); - $vp2->CopyContentFromID = $page->ID; - $vp2->write(); - $vp2->doPublish(); - - // Switch back to main site, unpublish source - $subsite = $this->objFromFixture(Subsite::class, 'main'); - Subsite::changeSubsite($subsite->ID); - $page = $this->objFromFixture('Page', 'importantpage'); - $page->doUnpublish(); - - Subsite::changeSubsite($vp1->SubsiteID); - $onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp1->ID); - $this->assertNull($onLive, 'SVP has been removed from live'); - - $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); - Subsite::changeSubsite($vp2->SubsiteID); - $onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID); - $this->assertNull($onLive, 'SVP has been removed from live'); - } - - /** - * Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()} - * and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}. - */ - function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() { - Subsite::$write_hostmap = false; - $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); - $subsite2 = $this->objFromFixture(Subsite::class, 'subsite2'); - Subsite::changeSubsite($subsite1->ID); - - $subsite1Page = $this->objFromFixture('Page', 'subsite1_staff'); - $subsite1Page->URLSegment = 'staff'; - $subsite1Page->write(); - - // saving on subsite1, and linking to subsite1 - $subsite1Vp = new SubsitesVirtualPage(); - $subsite1Vp->CopyContentFromID = $subsite1Page->ID; - $subsite1Vp->SubsiteID = $subsite1->ID; - $subsite1Vp->write(); - $this->assertNotEquals( - $subsite1Vp->URLSegment, - $subsite1Page->URLSegment, - "Doesn't allow explicit URLSegment overrides when already existing in same subsite" - ); - - //Change to subsite 2 - Subsite::changeSubsite($subsite2->ID); - - // saving in subsite2 (which already has a page with URLSegment 'contact-us'), - // but linking to a page in subsite1 - $subsite2Vp = new SubsitesVirtualPage(); - $subsite2Vp->CopyContentFromID = $subsite1Page->ID; - $subsite2Vp->SubsiteID = $subsite2->ID; - $subsite2Vp->write(); - $this->assertEquals( - $subsite2Vp->URLSegment, - $subsite1Page->URLSegment, - "Does allow explicit URLSegment overrides when only existing in a different subsite" - ); - } - - function fixVersionNumberCache($page) { - $pages = func_get_args(); - foreach($pages as $p) { - Versioned::prepopulate_versionnumber_cache('SilverStripe\\CMS\\Model\\SiteTree', 'Stage', array($p->ID)); - Versioned::prepopulate_versionnumber_cache('SilverStripe\\CMS\\Model\\SiteTree', 'Live', array($p->ID)); - } - } +class SubsitesVirtualPageTest extends BaseSubsiteTest +{ + static $fixture_file = [ + 'subsites/tests/SubsiteTest.yml', + 'subsites/tests/SubsitesVirtualPageTest.yml', + ]; + + public function setUp() + { + parent::setUp(); + + // Set backend root to /DataDifferencerTest + TestAssetStore::activate('SubsitesVirtualPageTest'); + + // Create a test files for each of the fixture references + $file = $this->objFromFixture('SilverStripe\\Assets\\File', 'file1'); + $page = $this->objFromFixture('SilverStripe\\CMS\\Model\\SiteTree', 'page1'); + $fromPath = __DIR__ . '/testscript-test-file.pdf'; + $destPath = TestAssetStore::getLocalPath($file); + Filesystem::makeFolder(dirname($destPath)); + copy($fromPath, $destPath); + + // Hack in site link tracking after the fact + $page->Content = '

'; + $page->write(); + } + + public function tearDown() + { + TestAssetStore::reset(); + parent::tearDown(); + } + + // Attempt to bring main:linky to subsite2:linky + function testVirtualPageFromAnotherSubsite() + { + Subsite::$write_hostmap = false; + + $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); + + Subsite::changeSubsite($subsite->ID); + Subsite::$disable_subsite_filter = false; + + $linky = $this->objFromFixture('Page', 'linky'); + + $svp = new SubsitesVirtualPage(); + $svp->CopyContentFromID = $linky->ID; + $svp->SubsiteID = $subsite->ID; + $svp->URLSegment = 'linky'; + + $svp->write(); + + $this->assertEquals($svp->SubsiteID, $subsite->ID); + $this->assertEquals($svp->Title, $linky->Title); + } + + function testFileLinkRewritingOnVirtualPages() + { + // File setup + $this->logInWithPermission('ADMIN'); + + // Publish the source page + $page = $this->objFromFixture('SilverStripe\\CMS\\Model\\SiteTree', 'page1'); + $this->assertTrue($page->doPublish()); + + // Create a virtual page from it, and publish that + $svp = new SubsitesVirtualPage(); + $svp->CopyContentFromID = $page->ID; + $svp->write(); + $svp->doPublish(); + + // Rename the file + $file = $this->objFromFixture('SilverStripe\\Assets\\File', 'file1'); + $file->Name = 'renamed-test-file.pdf'; + $file->write(); + + // Verify that the draft and publish virtual pages both have the corrected link + $this->assertContains('ID")->value()); + $this->assertContains('ID")->value()); + } + + function testSubsiteVirtualPagesArentInappropriatelyPublished() + { + // Fixture + $p = new Page(); + $p->Content = "test content"; + $p->write(); + $vp = new SubsitesVirtualPage(); + $vp->CopyContentFromID = $p->ID; + $vp->write(); + + // VP is oragne + $this->assertTrue($vp->IsAddedToStage); + + // VP is still orange after we publish + $p->doPublish(); + $this->fixVersionNumberCache($vp); + $this->assertTrue($vp->IsAddedToStage); + + // A new VP created after P's initial construction + $vp2 = new SubsitesVirtualPage(); + $vp2->CopyContentFromID = $p->ID; + $vp2->write(); + $this->assertTrue($vp2->IsAddedToStage); + + // Also remains orange after a republish + $p->Content = "new content"; + $p->write(); + $p->doPublish(); + $this->fixVersionNumberCache($vp2); + $this->assertTrue($vp2->IsAddedToStage); + + // VP is now published + $vp->doPublish(); + + $this->fixVersionNumberCache($vp); + $this->assertTrue($vp->ExistsOnLive); + $this->assertFalse($vp->IsModifiedOnStage); + + // P edited, VP and P both go green + $p->Content = "third content"; + $p->write(); + + $this->fixVersionNumberCache($vp, $p); + $this->assertTrue($p->IsModifiedOnStage); + $this->assertTrue($vp->IsModifiedOnStage); + + // Publish, VP goes black + $p->doPublish(); + $this->fixVersionNumberCache($vp); + $this->assertTrue($vp->ExistsOnLive); + $this->assertFalse($vp->IsModifiedOnStage); + } + + /** + * This test ensures published Subsites Virtual Pages immediately reflect updates + * to their published target pages. Note - this has to happen when the virtual page + * is in a different subsite to the page you are editing and republishing, + * otherwise the test will pass falsely due to current subsite ID being the same. + */ + function testPublishedSubsiteVirtualPagesUpdateIfTargetPageUpdates() + { + // create page + $p = new Page(); + $p->Content = 'Content'; + $p->Title = 'Title'; + $p->writeToStage('Stage'); + $p->publish('Stage', 'Live'); + $this->assertTrue($p->ExistsOnLive); + + // change to subsite + $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); + Subsite::changeSubsite($subsite->ID); + Subsite::$disable_subsite_filter = false; + + // create svp in subsite + $svp = new SubsitesVirtualPage(); + $svp->CopyContentFromID = $p->ID; + $svp->write(); + $svp->writeToStage('Stage'); + $svp->publish('Stage', 'Live'); + $this->assertEquals($svp->SubsiteID, $subsite->ID); + $this->assertTrue($svp->ExistsOnLive); + + // change back to original subsite ("Main site") + Subsite::changeSubsite(0); + + // update original page + $p->Title = 'New Title'; + // "save & publish" + $p->writeToStage('Stage'); + $p->publish('Stage', 'Live'); + $this->assertNotEquals($p->SubsiteID, $subsite->ID); + + // reload SVP from database + // can't use DO::get by id because caches. + $svpdb = $svp->get()->byID($svp->ID); + + // ensure title changed + $this->assertEquals($svpdb->Title, $p->Title); + } + + function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() + { + Config::modify()->set('StaticPublisher', 'disable_realtime', true); + + // Go to main site, get parent page + $subsite = $this->objFromFixture(Subsite::class, 'main'); + Subsite::changeSubsite($subsite->ID); + $page = $this->objFromFixture('Page', 'importantpage'); + + // Create two SVPs on other subsites + $subsite = $this->objFromFixture(Subsite::class, 'subsite1'); + Subsite::changeSubsite($subsite->ID); + $vp1 = new SubsitesVirtualPage(); + $vp1->CopyContentFromID = $page->ID; + $vp1->write(); + $vp1->doPublish(); + + $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); + Subsite::changeSubsite($subsite->ID); + $vp2 = new SubsitesVirtualPage(); + $vp2->CopyContentFromID = $page->ID; + $vp2->write(); + $vp2->doPublish(); + + // Switch back to main site, unpublish source + $subsite = $this->objFromFixture(Subsite::class, 'main'); + Subsite::changeSubsite($subsite->ID); + $page = $this->objFromFixture('Page', 'importantpage'); + $page->doUnpublish(); + + Subsite::changeSubsite($vp1->SubsiteID); + $onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', + "\"SiteTree_Live\".\"ID\" = " . $vp1->ID); + $this->assertNull($onLive, 'SVP has been removed from live'); + + $subsite = $this->objFromFixture(Subsite::class, 'subsite2'); + Subsite::changeSubsite($vp2->SubsiteID); + $onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', + "\"SiteTree_Live\".\"ID\" = " . $vp2->ID); + $this->assertNull($onLive, 'SVP has been removed from live'); + } + + /** + * Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()} + * and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}. + */ + function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() + { + Subsite::$write_hostmap = false; + $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); + $subsite2 = $this->objFromFixture(Subsite::class, 'subsite2'); + Subsite::changeSubsite($subsite1->ID); + + $subsite1Page = $this->objFromFixture('Page', 'subsite1_staff'); + $subsite1Page->URLSegment = 'staff'; + $subsite1Page->write(); + + // saving on subsite1, and linking to subsite1 + $subsite1Vp = new SubsitesVirtualPage(); + $subsite1Vp->CopyContentFromID = $subsite1Page->ID; + $subsite1Vp->SubsiteID = $subsite1->ID; + $subsite1Vp->write(); + $this->assertNotEquals( + $subsite1Vp->URLSegment, + $subsite1Page->URLSegment, + "Doesn't allow explicit URLSegment overrides when already existing in same subsite" + ); + + //Change to subsite 2 + Subsite::changeSubsite($subsite2->ID); + + // saving in subsite2 (which already has a page with URLSegment 'contact-us'), + // but linking to a page in subsite1 + $subsite2Vp = new SubsitesVirtualPage(); + $subsite2Vp->CopyContentFromID = $subsite1Page->ID; + $subsite2Vp->SubsiteID = $subsite2->ID; + $subsite2Vp->write(); + $this->assertEquals( + $subsite2Vp->URLSegment, + $subsite1Page->URLSegment, + "Does allow explicit URLSegment overrides when only existing in a different subsite" + ); + } + + function fixVersionNumberCache($page) + { + $pages = func_get_args(); + foreach ($pages as $p) { + Versioned::prepopulate_versionnumber_cache('SilverStripe\\CMS\\Model\\SiteTree', 'Stage', [$p->ID]); + Versioned::prepopulate_versionnumber_cache('SilverStripe\\CMS\\Model\\SiteTree', 'Live', [$p->ID]); + } + } } diff --git a/tests/behat/features/bootstrap/Context/FeatureContext.php b/tests/behat/features/bootstrap/Context/FeatureContext.php index b5be73da..ff25046a 100644 --- a/tests/behat/features/bootstrap/Context/FeatureContext.php +++ b/tests/behat/features/bootstrap/Context/FeatureContext.php @@ -2,17 +2,18 @@ namespace Subsites\Test\Behaviour; -if(!class_exists('SilverStripe\BehatExtension\Context\SilverStripeContext')) return; - -use SilverStripe\BehatExtension\Context\SilverStripeContext, - SilverStripe\BehatExtension\Context\BasicContext, - SilverStripe\BehatExtension\Context\LoginContext, - SilverStripe\BehatExtension\Context\FixtureContext, - SilverStripe\Framework\Test\Behaviour\CmsFormsContext, - SilverStripe\Framework\Test\Behaviour\CmsUiContext, - SilverStripe\Cms\Test\Behaviour; -use SilverStripe\Core\Injector\Injector; +if (!class_exists('SilverStripe\BehatExtension\Context\SilverStripeContext')) { + return; +} + +use SilverStripe\BehatExtension\Context\BasicContext; +use SilverStripe\BehatExtension\Context\FixtureContext; +use SilverStripe\BehatExtension\Context\LoginContext; +use SilverStripe\BehatExtension\Context\SilverStripeContext; use SilverStripe\Core\ClassInfo; +use SilverStripe\Core\Injector\Injector; +use SilverStripe\Framework\Test\Behaviour\CmsFormsContext; +use SilverStripe\Framework\Test\Behaviour\CmsUiContext; // PHPUnit @@ -25,8 +26,9 @@ * Context automatically loaded by Behat. * Uses subcontexts to extend functionality. */ -class FeatureContext extends SilverStripeContext { - +class FeatureContext extends SilverStripeContext +{ + /** * @var FixtureFactory */ @@ -38,7 +40,8 @@ class FeatureContext extends SilverStripeContext { * * @param array $parameters context parameters (set them up through behat.yml) */ - public function __construct(array $parameters) { + public function __construct(array $parameters) + { parent::__construct($parameters); $this->useContext('BasicContext', new BasicContext($parameters)); @@ -53,41 +56,46 @@ public function __construct(array $parameters) { // Use blueprints to set user name from identifier $factory = $fixtureContext->getFixtureFactory(); $blueprint = Injector::inst()->create('SilverStripe\\Dev\\FixtureBlueprint', 'SilverStripe\\Security\\Member'); - $blueprint->addCallback('beforeCreate', function($identifier, &$data, &$fixtures) { - if(!isset($data['FirstName'])) $data['FirstName'] = $identifier; + $blueprint->addCallback('beforeCreate', function ($identifier, &$data, &$fixtures) { + if (!isset($data['FirstName'])) { + $data['FirstName'] = $identifier; + } }); $factory->define('SilverStripe\\Security\\Member', $blueprint); // Auto-publish pages - foreach(ClassInfo::subclassesFor('SilverStripe\\CMS\\Model\\SiteTree') as $id => $class) { + foreach (ClassInfo::subclassesFor('SilverStripe\\CMS\\Model\\SiteTree') as $id => $class) { $blueprint = Injector::inst()->create('SilverStripe\\Dev\\FixtureBlueprint', $class); - $blueprint->addCallback('afterCreate', function($obj, $identifier, &$data, &$fixtures) { + $blueprint->addCallback('afterCreate', function ($obj, $identifier, &$data, &$fixtures) { $obj->publish('Stage', 'Live'); }); $factory->define($class, $blueprint); - } + } } - public function setMinkParameters(array $parameters) { + public function setMinkParameters(array $parameters) + { parent::setMinkParameters($parameters); - - if(isset($parameters['files_path'])) { - $this->getSubcontext('FixtureContext')->setFilesPath($parameters['files_path']); + + if (isset($parameters['files_path'])) { + $this->getSubcontext('FixtureContext')->setFilesPath($parameters['files_path']); } } /** * @return FixtureFactory */ - public function getFixtureFactory() { - if(!$this->fixtureFactory) { + public function getFixtureFactory() + { + if (!$this->fixtureFactory) { $this->fixtureFactory = Injector::inst()->create('SilverStripe\\Dev\\BehatFixtureFactory'); } return $this->fixtureFactory; } - public function setFixtureFactory(FixtureFactory $factory) { + public function setFixtureFactory(FixtureFactory $factory) + { $this->fixtureFactory = $factory; } diff --git a/tests/behat/features/preview-navigation.feature b/tests/behat/features/preview-navigation.feature index 5e36909f..61d1ba58 100644 --- a/tests/behat/features/preview-navigation.feature +++ b/tests/behat/features/preview-navigation.feature @@ -1,26 +1,26 @@ Feature: Preview navigation - As a CMS user - I can navigate a subsite in the preview pane - In order to preview my content + As a CMS user + I can navigate a subsite in the preview pane + In order to preview my content - Background: - Given a "subsite" "My subsite" - And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content anameahref" and "Subsite"="=>Subsite.My subsite" - And a "page" "Other page" with "URLSegment"="other-page", "Content"="Other page content
" and "Subsite"="=>Subsite.My subsite" - Given a "member" "Joe" belonging to "Admin Group" with "Email"="joe@test.com" and "Password"="Password1" - And the "group" "Admin Group" has permissions "Full administrative rights" - And I log in with "joe@test.com" and "Password1" + Background: + Given a "subsite" "My subsite" + And a "page" "My page" with "URLSegment"="my-page", "Content"="My page content anameahref" and "Subsite"="=>Subsite.My subsite" + And a "page" "Other page" with "URLSegment"="other-page", "Content"="Other page content
" and "Subsite"="=>Subsite.My subsite" + Given a "member" "Joe" belonging to "Admin Group" with "Email"="joe@test.com" and "Password"="Password1" + And the "group" "Admin Group" has permissions "Full administrative rights" + And I log in with "joe@test.com" and "Password1" - Scenario: I can navigate the subsite preview - When I go to "admin" - And I select "My subsite" from "SubsitesSelect" - And I go to "admin/pages" - And I click on "My page" in the tree - And I wait for 3 seconds - And I set the CMS mode to "Preview mode" - And I follow "ahref" in preview - Then the preview contains "Other page content" - # We are already on the second page, submit the form to return to first one. - When I wait for 3 seconds - And I press "Submit my form" in preview - Then the preview contains "My page content" + Scenario: I can navigate the subsite preview + When I go to "admin" + And I select "My subsite" from "SubsitesSelect" + And I go to "admin/pages" + And I click on "My page" in the tree + And I wait for 3 seconds + And I set the CMS mode to "Preview mode" + And I follow "ahref" in preview + Then the preview contains "Other page content" + # We are already on the second page, submit the form to return to first one. + When I wait for 3 seconds + And I press "Submit my form" in preview + Then the preview contains "My page content"