diff --git a/.travis.yml b/.travis.yml index 8b1afd7..619ac95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ language: php sudo: false +sudo: false + php: - 5.5 diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index e69de29..0000000 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ac82de3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +This project adheres to [Semantic Versioning](http://semver.org/). + +## [1.2.3] + +* BUG Fix issue with urlsegment being renamed in subsites + +## [1.2.2] + +* Update translations. +* Added attributes to menu link + +## [1.2.1] + +* BUG: The move to subsite folder dropdown in files is gone +* Update templates for 3.3 compatibility +* Update userhelp documentation +* Fix Subsite module does not picks up themes +* Update translations + +## [1.2.0] + +* API Add option to specify http / https on subsite domains + +## [1.1.0] + +* Changelog added. +* Fixes #135: LeftAndMain switching between subsites +* BUG Fix incompatibility with framework 3.2 +* Adjusted tests to new SiteTree->canCreate() logic in 3.1.11+ +* Fix subsites to use correct permissions +* Wrong edit link in SubsitesVirtualPage +* Added missing route to `SubsiteXHRController` for SilverStripe 3.2 compatibility. +* Add sticky nav toggle button +* BUG Subsites selection on SubsitesVirtualPage (fixes #45 and #47) +* Update translations diff --git a/README.md b/README.md index fea12af..a41737a 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ permissions" will imply that the person will likely be able to escalate his/her For user documentation please see: - 1. [Setting up subsites](docs/en/set_up.md) - 1. [Working with subsites](docs/en/working_with.md) + 1. [Setting up subsites](docs/en/userguide/set_up.md) + 1. [Working with subsites](docs/en/userguide/working_with.md) ## Features & limitations @@ -41,7 +41,7 @@ For user documentation please see: * Each subsite domain name has to be set up on the server first, and DNS records need to be updated as appropriate. * A subsite cannot use a different codebase as the main site, they are intrinsically tied - * However, you can remove page types from a subsite when creating the subsite - [see the setup documentation for further details](set_up.md) + * However, you can remove page types from a subsite when creating the subsite - [see the setup documentation for further details](docs/en/userguide/set_up.md) * The only code a developer can edit between subsites is the theme * The separation between subsites in the CMS needs to be seen as cosmetic, and mostly applicable to the "Pages" and "Files" sections of the CMS. * All subsites run in the same process space and data set. Therefore if an outage affects one subsite it will affect all subsites, and if bad code or hardware corrupts one subsite's data, it's very likely that it has corrupted all subsite data. diff --git a/code/SubsiteAdmin.php b/code/SubsiteAdmin.php new file mode 100644 index 0000000..6fd0915 --- /dev/null +++ b/code/SubsiteAdmin.php @@ -0,0 +1,36 @@ +Fields()->dataFieldByName('Subsite'); + if ($grid) { + $grid->getConfig()->removeComponentsByType(GridFieldDetailForm::class); + $grid->getConfig()->addComponent(new GridFieldSubsiteDetailForm()); + } + + return $form; + } +} diff --git a/code/SubsiteReportWrapper.php b/code/SubsiteReportWrapper.php new file mode 100644 index 0000000..d35b884 --- /dev/null +++ b/code/SubsiteReportWrapper.php @@ -0,0 +1,73 @@ +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 + + public function columns() + { + $columns = parent::columns(); + $columns['Subsite.Title'] = "Subsite"; + return $columns; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Querying + + public 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)); + } + } + public function afterQuery() + { + // Manually manage the subsite filtering + Subsite::$force_subsite = null; + } +} diff --git a/code/SubsiteXHRController.php b/code/SubsiteXHRController.php new file mode 100644 index 0000000..3a00a98 --- /dev/null +++ b/code/SubsiteXHRController.php @@ -0,0 +1,59 @@ +count()>0) { + return true; + } + + return false; + } + + /** + * Allow access if user allowed into the CMS at all. + */ + public function canAccess() + { + // Allow if any cms access is available + return Permission::check(array( + 'CMS_ACCESS', // Supported by 3.1.14 and up + 'CMS_ACCESS_LeftAndMain' + )); + } + + 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('SubsiteList'); + } +} diff --git a/code/SubsitesVirtualPage.php b/code/SubsitesVirtualPage.php new file mode 100644 index 0000000..36e28e3 --- /dev/null +++ b/code/SubsitesVirtualPage.php @@ -0,0 +1,206 @@ + 'Varchar(255)', + 'CustomMetaKeywords' => 'Varchar(255)', + 'CustomMetaDescription' => 'Text', + 'CustomExtraMeta' => 'HTMLText' + ); + + public function getCMSFields() + { + $fields = parent::getCMSFields(); + + $subsites = DataObject::get('Subsite'); + 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"), + $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"), + SiteTree::class, + "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(SiteTree::class, $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; + } + } +} + +//class SubsitesVirtualPage_Controller extends VirtualPage_Controller +//{ +// public function reloadContent() +// { +// $this->failover->copyFrom($this->failover->CopyContentFrom()); +// $this->failover->write(); +// return; +// } +// +// public function init() +// { +// $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; +// Subsite::$disable_subsite_filter = true; +// +// parent::init(); +// +// Subsite::$disable_subsite_filter = $origDisableSubsiteFilter; +// } +//} diff --git a/code/extensions/ControllerSubsites.php b/code/extensions/ControllerSubsites.php index 14be6b9..f70e79f 100644 --- a/code/extensions/ControllerSubsites.php +++ b/code/extensions/ControllerSubsites.php @@ -12,7 +12,7 @@ use SilverStripe\View\SSViewer; */ class ControllerSubsites extends Extension { - function controllerAugmentInit() + public function controllerAugmentInit() { if ($subsite = Subsite::currentSubsite()) { if ($theme = $subsite->Theme) { @@ -21,12 +21,10 @@ class ControllerSubsites extends Extension } } - function CurrentSubsite() + public function CurrentSubsite() { if ($subsite = Subsite::currentSubsite()) { return $subsite; } } } - -?> diff --git a/code/extensions/ErrorPageSubsite.php b/code/extensions/ErrorPageSubsite.php index ec59106..3c9b17c 100644 --- a/code/extensions/ErrorPageSubsite.php +++ b/code/extensions/ErrorPageSubsite.php @@ -2,28 +2,26 @@ namespace SilverStripe\Subsites\Extensions; - -use SilverStripe\ORM\DataExtension; +use SilverStripe\Core\Config\Config; use SilverStripe\ORM\DataObject; +use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\ORM\DataExtension; 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()} + * @see Error::get_filepath_for_errorcode() * * 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) + public function alternateFilepathForErrorcode($statusCode, $locale = null) { + $static_filepath = Config::inst()->get($this->owner->ClassName, 'static_filepath'); + $subdomainPart = ""; // Try to get current subsite from session $subsite = Subsite::currentSubsite(false); @@ -32,17 +30,23 @@ class ErrorPageSubsite extends DataExtension if (!$subsite) { $subsiteID = Subsite::getSubsiteIDForDomain(); if ($subsiteID != 0) { - $subsite = DataObject::get_by_id(Subsite::class, $subsiteID); + $subsite = DataObject::get_by_id("Subsite", $subsiteID); + } else { + $subsite = null; } } - // 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"; + $subdomainPart = "-{$subdomain}"; } - } + if (singleton(SiteTree::class)->hasExtension('Translatable') && $locale && $locale != Translatable::default_locale()) { + $filepath = $static_filepath . "/error-{$statusCode}-{$locale}{$subdomainPart}.html"; + } else { + $filepath = $static_filepath . "/error-{$statusCode}{$subdomainPart}.html"; + } + + return $filepath; + } } diff --git a/code/extensions/FileSubsites.php b/code/extensions/FileSubsites.php index d4810ae..72c596a 100644 --- a/code/extensions/FileSubsites.php +++ b/code/extensions/FileSubsites.php @@ -21,61 +21,59 @@ use SilverStripe\Subsites\Model\Subsite; * @package subsites */ class FileSubsites extends DataExtension -{ + { + // If this is set to true, all folders created will be default be + // considered 'global', unless set otherwise + public static $default_root_folders_global = false; - // 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, + ]; - 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() + /** + * Amends the CMS tree title for folders in the Files & Images section. + * Prefixes a '* ' to the folders that are accessible from all subsites. + */ + public function alternateTreeTitle() { if ($this->owner->SubsiteID == 0) { return " * " . $this->owner->Title; - } else { - return $this->owner->Title; } + + return $this->owner->Title; } - /** - * Add subsites-specific fields to the folder editor. - */ - function updateCMSFields(FieldList $fields) + /** + * Add subsites-specific fields to the folder editor. + */ + public 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', - '
' - )); - } - } - } + 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', + ' ' + )); + } + } + } /** * Update any requests to limit the results to the current site @@ -112,50 +110,49 @@ class FileSubsites extends DataExtension } } - function onBeforeWrite() + public 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(); - } - } - } + 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() + public 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(); - } + if ($this->owner->Parent()) { + $this->owner->SubsiteID = $this->owner->Parent()->SubsiteID; + } else { + $this->owner->SubsiteID = Subsite::currentSubsiteID(); + } + $this->owner->write(); + } - function canEdit($member = null) + public 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); + $subsiteID = Session::get('SubsiteID'); + if($subsiteID&&$subsiteID == $this->owner->SubsiteID) { + return true; + } - return $access; - } - } + Session::set('SubsiteID', $this->owner->SubsiteID); + $access = Permission::check(['CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain']); + Session::set('SubsiteID', $subsiteID); - /** - * Return a piece of text to keep DataObject cache keys appropriately specific - */ - function cacheKeyComponent() + return $access; + } + + /** + * Return a piece of text to keep DataObject cache keys appropriately specific + */ + public function cacheKeyComponent() { - return 'subsite-' . Subsite::currentSubsiteID(); - } + return 'subsite-'.Subsite::currentSubsiteID(); + } } - diff --git a/code/extensions/GroupSubsites.php b/code/extensions/GroupSubsites.php index 0ee18d2..3c96d83 100644 --- a/code/extensions/GroupSubsites.php +++ b/code/extensions/GroupSubsites.php @@ -25,23 +25,22 @@ use SilverStripe\Subsites\Model\Subsite; */ class GroupSubsites extends DataExtension implements PermissionProvider { + private static $db = [ + 'AccessAllSubsites' => 'Boolean' + ]; - private static $db = [ - 'AccessAllSubsites' => 'Boolean' - ]; + private static $many_many = [ + 'Subsites' => Subsite::class + ]; - private static $many_many = [ - 'Subsites' => Subsite::class - ]; - - private static $defaults = [ - 'AccessAllSubsites' => true - ]; + private static $defaults = [ + 'AccessAllSubsites' => true + ]; /** * Migrations for GroupSubsites data. */ - function requireDefaultRecords() + public function requireDefaultRecords() { // Migration for Group.SubsiteID data from when Groups only had a single subsite $schema = $this->owner->getSchema(); @@ -65,15 +64,13 @@ class GroupSubsites extends DataExtension implements PermissionProvider 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() - ) { - + OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()) { DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1'); } } } - function updateCMSFields(FieldList $fields) + public function updateCMSFields(FieldList $fields) { if ($this->owner->canEdit()) { // i18n tab @@ -82,37 +79,37 @@ class GroupSubsites extends DataExtension implements PermissionProvider $subsites = Subsite::accessible_sites(['ADMIN', 'SECURITY_SUBSITE_GROUP'], true); $subsiteMap = $subsites->map(); - // Prevent XSS injection - $subsiteMap = Convert::raw2xml($subsiteMap); + // Prevent XSS injection + $subsiteMap = Convert::raw2xml($subsiteMap->toArray()); - // 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"), - ] - )); + // 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)); + 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)); - } - } - } - } + } 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, @@ -120,7 +117,7 @@ class GroupSubsites extends DataExtension implements PermissionProvider * to make it easy to distinguish in the tree-view * of the security admin interface. */ - function alternateTreeTitle() + public function alternateTreeTitle() { if ($this->owner->AccessAllSubsites) { $title = _t('GroupSubsites.GlobalGroup', 'global group'); @@ -176,12 +173,12 @@ class GroupSubsites extends DataExtension implements PermissionProvider // 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; + $query->addOrderBy("AccessAllSubsites", "DESC"); } } } - function onBeforeWrite() + public 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 @@ -190,7 +187,7 @@ class GroupSubsites extends DataExtension implements PermissionProvider } } - function onAfterWrite() + public 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 @@ -200,7 +197,7 @@ class GroupSubsites extends DataExtension implements PermissionProvider } } - function alternateCanEdit() + public 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'); @@ -211,7 +208,7 @@ class GroupSubsites extends DataExtension implements PermissionProvider return (bool)array_intersect($accessibleSites, $linkedSites); } - function providePermissions() + public function providePermissions() { return [ 'SECURITY_SUBSITE_GROUP' => [ @@ -225,5 +222,3 @@ class GroupSubsites extends DataExtension implements PermissionProvider } } - -?> diff --git a/code/extensions/LeftAndMainSubsites.php b/code/extensions/LeftAndMainSubsites.php index 810fd8a..e76b474 100644 --- a/code/extensions/LeftAndMainSubsites.php +++ b/code/extensions/LeftAndMainSubsites.php @@ -1,25 +1,20 @@ Title) : _t('LeftAndMain.SITECONTENTLEFT'); } - function updatePageOptions(&$fields) + public function updatePageOptions(&$fields) { $fields->push(new HiddenField('SubsiteID', 'SubsiteID', Subsite::currentSubsiteID())); } @@ -63,7 +57,7 @@ class LeftAndMainSubsites extends Extension * * @return ArrayList of {@link Subsite} instances. */ - function sectionSites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) + public function sectionSites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null) { if ($mainSiteTitle == 'Main site') { $mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site'); @@ -77,12 +71,12 @@ class LeftAndMainSubsites extends Extension return new ArrayList(); } if (!is_object($member)) { - $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); + $member = DataObject::get_by_id(Member::class, $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 = []; + $codes = array(); $extraCodes = Config::inst()->get($this->owner->class, 'required_permission_codes'); if ($extraCodes !== false) { if ($extraCodes) { @@ -96,8 +90,8 @@ class LeftAndMainSubsites extends Extension } // Find subsites satisfying all permissions for the Member. - $codesPerSite = []; - $sitesArray = []; + $codesPerSite = array(); + $sitesArray = array(); foreach ($codes as $code) { $sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member); foreach ($sites as $site) { @@ -112,7 +106,7 @@ class LeftAndMainSubsites extends Extension // Find sites that satisfy all codes conjuncitvely. $accessibleSites = new ArrayList(); foreach ($codesPerSite as $siteID => $siteCodes) { - if (count($siteCodes) == count($codes)) { + if (count($siteCodes)==count($codes)) { $accessibleSites->push($sitesArray[$siteID]); } } @@ -151,11 +145,11 @@ class LeftAndMainSubsites extends Extension foreach ($list as $subsite) { $CurrentState = $subsite->ID == $currentSubsiteID ? 'selected' : ''; - $output->push(new ArrayData([ + $output->push(new ArrayData(array( 'CurrentState' => $CurrentState, 'ID' => $subsite->ID, 'Title' => Convert::raw2xml($subsite->Title) - ])); + ))); } return $output; @@ -194,10 +188,10 @@ class LeftAndMainSubsites extends Extension */ public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID) { - if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID == 0) { + if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID==0) { return false; } - if ($recordSubsiteID != $currentSubsiteID) { + if ($recordSubsiteID!=$currentSubsiteID) { return true; } return false; @@ -206,17 +200,15 @@ class LeftAndMainSubsites extends Extension /** * Check if the current controller is accessible for this user on this subsite. */ - function canAccess() + public 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 - ) - ) { + ( + Permission::checkMember($member, 'ADMIN') || // 'Full administrative rights' in SecurityAdmin + Permission::checkMember($member, 'CMS_ACCESS_LeftAndMain') // 'Access to all CMS sections' in SecurityAdmin + )) { return true; } @@ -279,7 +271,6 @@ class LeftAndMainSubsites extends Extension // 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); @@ -291,20 +282,17 @@ class LeftAndMainSubsites extends Extension //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) { - + 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. @@ -326,34 +314,41 @@ class LeftAndMainSubsites extends Extension // 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) + public function augmentNewSiteTreeItem(&$item) { $item->SubsiteID = isset($_POST['SubsiteID']) ? $_POST['SubsiteID'] : Subsite::currentSubsiteID(); } - function onAfterSave($record) + public 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.'))); + $this->owner->response->addHeader('X-Status', rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.'))); } } - function copytosubsite($data, $form) + /** + * @param array $data + * @param Form $form + */ + public 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); + $page = DataObject::get_by_id(SiteTree::class, $data['ID']); + $subsite = DataObject::get_by_id('Subsite', $data['CopyToSubsiteID']); + $includeChildren = (isset($data['CopyToSubsiteWithChildren'])) ? $data['CopyToSubsiteWithChildren'] : false; + + $newPage = $page->duplicateToSubsite($subsite->ID, $includeChildren); $response = $this->owner->getResponse(); $response->addHeader('X-Reload', true); - return $this->owner->redirect(Controller::join_links($this->owner->Link('show'), $newPage->ID)); - } + 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 ea20584..3dbbb17 100644 --- a/code/extensions/SiteConfigSubsites.php +++ b/code/extensions/SiteConfigSubsites.php @@ -17,19 +17,15 @@ use SilverStripe\Subsites\Model\Subsite; */ class SiteConfigSubsites extends DataExtension { + private static $has_one = [ + 'Subsite' => Subsite::class, // The subsite that this page belongs to + ]; - 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; - } + /** + * 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()) { @@ -42,20 +38,16 @@ class SiteConfigSubsites extends DataExtension } } - /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; - else */ - $subsiteID = (int)Subsite::currentSubsiteID(); + $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)"); - } + $froms=$query->getFrom(); + $froms=array_keys($froms); + $tableName = array_shift($froms); + if($tableName != SiteConfig::class) { return;} + $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)"); + } - function onBeforeWrite() + public function onBeforeWrite() { if ((!is_numeric($this->owner->ID) || !$this->owner->ID) && !$this->owner->SubsiteID) { $this->owner->SubsiteID = Subsite::currentSubsiteID(); @@ -65,12 +57,12 @@ class SiteConfigSubsites extends DataExtension /** * Return a piece of text to keep DataObject cache keys appropriately specific */ - function cacheKeyComponent() + public function cacheKeyComponent() { - return 'subsite-' . Subsite::currentSubsiteID(); + return 'subsite-'.Subsite::currentSubsiteID(); } - function updateCMSFields(FieldList $fields) + public 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 8885e01..530cd27 100644 --- a/code/extensions/SiteTreeSubsites.php +++ b/code/extensions/SiteTreeSubsites.php @@ -1,47 +1,42 @@ 'Subsite', // The subsite that this page belongs to + ); - private static $has_one = [ - 'Subsite' => Subsite::class, // The subsite that this page belongs to - ]; + private static $many_many = array( + 'CrossSubsiteLinkTracking' => SiteTree::class // Stored separately, as the logic for URL rewriting is different + ); - private static $many_many = [ - '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") + ); - private static $many_many_extraFields = [ - "CrossSubsiteLinkTracking" => ["FieldName" => "Varchar"] - ]; - - function isMainSite() + public function isMainSite() { if ($this->owner->SubsiteID == 0) { return true; @@ -49,15 +44,17 @@ class SiteTreeSubsites extends DataExtension return false; } - /** - * Update any requests to limit the results to the current site - */ + /** + * Update any requests to limit the results to the current site + * @param SQLSelect $query + * @param DataQuery $dataQuery + */ public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) { if (Subsite::$disable_subsite_filter) { return; } - if ($dataQuery->getQueryParam('Subsite.filter') === false) { + if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) { return; } @@ -71,8 +68,7 @@ class SiteTreeSubsites extends DataExtension $subsiteID = Subsite::$force_subsite; } else { /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; - else */ - $subsiteID = (int)Subsite::currentSubsiteID(); + else */$subsiteID = (int)Subsite::currentSubsiteID(); } // The foreach is an ugly way of getting the first key :-) @@ -86,7 +82,7 @@ class SiteTreeSubsites extends DataExtension } } - function onBeforeWrite() + public function onBeforeWrite() { if (!$this->owner->ID && !$this->owner->SubsiteID) { $this->owner->SubsiteID = Subsite::currentSubsiteID(); @@ -95,57 +91,67 @@ class SiteTreeSubsites extends DataExtension parent::onBeforeWrite(); } - function updateCMSFields(FieldList $fields) + public function updateCMSFields(FieldList $fields) { $subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain"); - $subsitesMap = []; + $subsitesMap = array(); if ($subsites && $subsites->Count()) { - $subsitesMap = $subsites->map('ID', 'Title')->toArray(); - unset($subsitesMap[$this->owner->SubsiteID]); + $subsitesToMap = $subsites->exclude('ID', $this->owner->SubsiteID); + $subsitesMap = $subsitesToMap->map('ID', 'Title'); } // 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( + $fields->addFieldsToTab( '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") - ) + ToggleCompositeField::create('SubsiteOperations', + _t('SiteTreeSubsites.SubsiteOperations', 'Subsite Operations'), + array( + new DropdownField("CopyToSubsiteID", _t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"), $subsitesMap), + new CheckboxField("CopyToSubsiteWithChildren", _t('SiteTreeSubsites.CopyToSubsiteWithChildren', 'Include children pages?')), + $copyAction = new InlineFormAction( + "copytosubsite", + _t('SiteTreeSubsites.CopyAction', "Copy") + ) + ) + )->setHeadingLevel(4) ); + + +// $copyAction->includeDefaultJS(false); } // 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) - ); + $nested_urls_enabled = Config::inst()->get(SiteTree::class, 'nested_urls'); + if ($subsite && $subsite->exists()) { + // Use baseurl from domain + $baseLink = $subsite->absoluteBaseURL(); + + // Add parent page if enabled + if($nested_urls_enabled && $this->owner->ParentID) { + $baseLink = Controller::join_links( + $baseLink, + $this->owner->Parent()->RelativeLink(true) + ); + } $urlsegment = $fields->dataFieldByName('URLSegment'); $urlsegment->setURLPrefix($baseLink); } } - function alternateSiteConfig() + /** + * @return SiteConfig + */ + public function alternateSiteConfig() { if (!$this->owner->SubsiteID) { return false; } - $sc = DataObject::get_one('SilverStripe\\SiteConfig\\SiteConfig', '"SubsiteID" = ' . $this->owner->SubsiteID); + $sc = DataObject::get_one(SiteConfig::class, '"SubsiteID" = ' . $this->owner->SubsiteID); if (!$sc) { $sc = new SiteConfig(); $sc->SubsiteID = $this->owner->SubsiteID; @@ -163,9 +169,8 @@ class SiteTreeSubsites extends DataExtension * * @return boolean */ - function canEdit($member = null) + public function canEdit($member = null) { - if (!$member) { $member = Member::currentUser(); } @@ -193,9 +198,9 @@ class SiteTreeSubsites extends DataExtension /** * @return boolean */ - function canDelete($member = null) + public function canDelete($member = null) { - if (!$member && $member !== FALSE) { + if (!$member && $member !== false) { $member = Member::currentUser(); } @@ -205,9 +210,9 @@ class SiteTreeSubsites extends DataExtension /** * @return boolean */ - function canAddChildren($member = null) + public function canAddChildren($member = null) { - if (!$member && $member !== FALSE) { + if (!$member && $member !== false) { $member = Member::currentUser(); } @@ -217,9 +222,9 @@ class SiteTreeSubsites extends DataExtension /** * @return boolean */ - function canPublish($member = null) + public function canPublish($member = null) { - if (!$member && $member !== FALSE) { + if (!$member && $member !== false) { $member = Member::currentUser(); } @@ -227,104 +232,70 @@ class SiteTreeSubsites extends DataExtension } /** - * Does the basic duplication, but doesn't write anything - * this means we can subclass this easier and do more complex - * relation duplication. + * Create a duplicate of this page and save it to another subsite + * + * @param int|Subsite $subsiteID The Subsite to copy to, or its ID + * @param bool $includeChildren Recursively copy child Pages. + * @param int $parentID Where to place the Page in the SiteTree's structure. + * + * @return SiteTree duplicated page */ - public function duplicateToSubsitePrep($subsiteID) + public function duplicateToSubsite($subsiteID = null, $includeChildren = false, $parentID = 0) { - if (is_object($subsiteID)) { + if ($subsiteID instanceof Subsite) { $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; + + $page = $this->owner->duplicate(false); + + $page->CheckedPublicationDifferences = $page->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; - } - } + $page->SubsiteID = $subsiteID; + + $page->ParentID = $parentID; + // MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module - $clone->MasterPageID = $this->owner->ID; - return $clone; - } + $page->MasterPageID = $this->owner->ID; + $page->write(); - /** - * 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; - } + Subsite::changeSubsite($oldSubsite); - /** - * 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($includeChildren) { + foreach($this->owner->AllChildren() as $child) { + $child->duplicateToSubsite($subsiteID, $includeChildren, $page->ID); + } + } - 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(); - } - } - } + return $page; } /** * Called by ContentController::init(); */ - static function contentcontrollerInit($controller) + public static function contentcontrollerInit($controller) { $subsite = Subsite::currentSubsite(); if ($subsite && $subsite->Theme) { - Config::modify()->set('SilverStripe\\View\\SSViewer', 'theme', Subsite::currentSubsite()->Theme); + Config::inst()->update(SSViewer::class, 'theme', Subsite::currentSubsite()->Theme); } } - function alternateAbsoluteLink() + public 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); + $url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url); } return $url; } @@ -333,7 +304,7 @@ class SiteTreeSubsites extends DataExtension * Use the CMS domain for iframed CMS previews to prevent single-origin violations * and SSL cert problems. */ - function alternatePreviewLink($action = null) + public function alternatePreviewLink($action = null) { $url = Director::absoluteURL($this->owner->Link()); if ($this->owner->SubsiteID) { @@ -345,7 +316,7 @@ class SiteTreeSubsites extends DataExtension /** * Inject the subsite ID into the content so it can be used by frontend scripts. */ - function MetaTags(&$tags) + public function MetaTags(&$tags) { if ($this->owner->SubsiteID) { $tags .= "owner->SubsiteID . "\" />\n"; @@ -354,11 +325,11 @@ class SiteTreeSubsites extends DataExtension return $tags; } - function augmentSyncLinkTracking() + public function augmentSyncLinkTracking() { // Set LinkTracking appropriately $links = HTTP::getLinksIn($this->owner->Content); - $linkedPages = []; + $linkedPages = array(); if ($links) { foreach ($links as $link) { @@ -373,11 +344,9 @@ class SiteTreeSubsites extends DataExtension continue; } // We have no idea what the domain for the main site is, so cant track links to it - $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; + $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); + $candidatePage = DataObject::get_one(SiteTree::class, "\"URLSegment\" = '" . Convert::raw2sql(urldecode($rest)) . "' AND \"SubsiteID\" = " . $subsiteID, false); Subsite::disable_subsite_filter($origDisableSubsiteFilter); if ($candidatePage) { @@ -393,19 +362,46 @@ class SiteTreeSubsites extends DataExtension $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages); } + /** + * Ensure that valid url segments are checked within the correct subsite of the owner object, + * even if the current subsiteID is set to some other subsite. + * + * @return null|bool Either true or false, or null to not influence result + */ + public function augmentValidURLSegment() + { + // If this page is being filtered in the current subsite, then no custom validation query is required. + $subsite = Subsite::$force_subsite ?: Subsite::currentSubsiteID(); + if (empty($this->owner->SubsiteID) || $subsite == $this->owner->SubsiteID) { + return null; + } + + // Backup forced subsite + $prevForceSubsite = Subsite::$force_subsite; + Subsite::$force_subsite = $this->owner->SubsiteID; + + // Repeat validation in the correct subsite + $isValid = $this->owner->validURLSegment(); + + // Restore + Subsite::$force_subsite = $prevForceSubsite; + + return (bool)$isValid; + } + /** * Return a piece of text to keep DataObject cache keys appropriately specific */ - function cacheKeyComponent() + public function cacheKeyComponent() { - return 'subsite-' . Subsite::currentSubsiteID(); + return 'subsite-'.Subsite::currentSubsiteID(); } /** * @param Member * @return boolean|null */ - function canCreate($member = null) + public function canCreate($member = null) { // Typically called on a singleton, so we're not using the Subsite() relation $subsite = Subsite::currentSubsite(); diff --git a/code/forms/GridFieldSubsiteDetailForm.php b/code/forms/GridFieldSubsiteDetailForm.php index 946204c..6c4c619 100644 --- a/code/forms/GridFieldSubsiteDetailForm.php +++ b/code/forms/GridFieldSubsiteDetailForm.php @@ -16,10 +16,9 @@ class GridFieldSubsiteDetailForm extends GridFieldDetailForm class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest { - - private static $allowed_actions = [ - 'ItemEditForm', - ]; + private static $allowed_actions = [ + 'ItemEditForm', + ]; /** * Builds an item edit form. The arguments to getCMSFields() are the popupController and @@ -32,7 +31,7 @@ class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemReq * @return Form * @see GridFieldDetailForm_ItemRequest::ItemEditForm() */ - function ItemEditForm() + public function ItemEditForm() { $form = parent::ItemEditForm(); @@ -52,7 +51,7 @@ class GridFieldSubsiteDetailForm_ItemRequest extends GridFieldDetailForm_ItemReq return $form; } - function doSave($data, $form) + public function doSave($data, $form) { $new_record = $this->record->ID == 0; if ($new_record && isset($data['TemplateID']) && !empty($data['TemplateID'])) { diff --git a/code/forms/SubsitesTreeDropdownField.php b/code/forms/SubsitesTreeDropdownField.php index 7b190d9..8ab86ef 100644 --- a/code/forms/SubsitesTreeDropdownField.php +++ b/code/forms/SubsitesTreeDropdownField.php @@ -17,16 +17,15 @@ use SilverStripe\View\Requirements; */ class SubsitesTreeDropdownField extends TreeDropdownField { - - private static $allowed_actions = [ + private static $allowed_actions = array( 'tree' - ]; + ); protected $subsiteID = 0; - protected $extraClasses = [SubsitesTreeDropdownField::class]; + protected $extraClasses = array('SubsitesTreeDropdownField'); - function Field($properties = []) + public function Field($properties = array()) { $html = parent::Field($properties); @@ -35,17 +34,17 @@ class SubsitesTreeDropdownField extends TreeDropdownField return $html; } - function setSubsiteID($id) + public function setSubsiteID($id) { $this->subsiteID = $id; } - function getSubsiteID() + public function getSubsiteID() { return $this->subsiteID; } - function tree(HTTPRequest $request) + public function tree(HTTPRequest $request) { $oldSubsiteID = Session::get('SubsiteID'); Session::set('SubsiteID', $this->subsiteID); diff --git a/code/forms/WildcardDomainField.php b/code/forms/WildcardDomainField.php new file mode 100644 index 0000000..7e3c2e0 --- /dev/null +++ b/code/forms/WildcardDomainField.php @@ -0,0 +1,45 @@ +checkHostname($this->Value())) { + return true; + } + + $validator->validationError( + $this->getName(), + _t("DomainNameField.INVALID_DOMAIN", "Invalid domain name"), + "validation" + ); + return false; + } + + /** + * Check if the given hostname is valid. + * + * @param string $hostname + * @return bool True if this hostname is valid + */ + public function checkHostname($hostname) + { + return (bool)preg_match('/^([a-z0-9\*]+[\-\.])*([a-z0-9\*]+)$/', $hostname); + } + + public function Type() + { + return 'text wildcarddomain'; + } +} diff --git a/code/model/Subsite.php b/code/model/Subsite.php index d570e87..2b22298 100644 --- a/code/model/Subsite.php +++ b/code/model/Subsite.php @@ -8,6 +8,7 @@ use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Control\Director; use SilverStripe\Control\Session; use SilverStripe\Core\Convert; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\CheckboxSetField; use SilverStripe\Forms\DropdownField; @@ -29,6 +30,7 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; use SilverStripe\Security\Member; use SilverStripe\Security\Permission; +use SilverStripe\Security\Group; use SilverStripe\Versioned\Versioned; use UnexpectedValueException; @@ -104,8 +106,7 @@ class Subsite extends DataObject */ public static $check_is_public = true; - /** - * @return array + /*** @return array */ private static $summary_fields = [ 'Title', @@ -114,172 +115,154 @@ class Subsite extends DataObject ]; /** - * 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; - } + * 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()); - } + /** + * 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=' . _t('Subsite.DOMAINSAVEFIRST', - 'You can only add domains after saving for the first time') . '
' + ''._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'
' ); } $languageSelector = new DropdownField( 'Language', $this->fieldLabel('Language'), - (new IntlLocales)->getLocales() + Injector::inst()->get(IntlLocales::class)->getLocales() ); - $pageTypeMap = []; + $pageTypeMap = array(); $pageTypes = SiteTree::page_type_classes(); foreach ($pageTypes as $pageType) { $pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name(); @@ -679,11 +642,11 @@ class Subsite extends DataObject new Tab( 'Configuration', _t('Subsite.TabTitleConfig', 'Configuration'), - new HeaderField('ConfigurationHeader', $this->getClassName() . ' configuration', 2), + new HeaderField('ConfigForSubsiteHeaderField', $this->getClassName() . ' configuration'), new TextField('Title', $this->fieldLabel('Title'), $this->Title), new HeaderField( - 'DomainsHeader', + 'DomainsForSubsiteHeaderField', _t('Subsite.DomainsHeadline', "Domains for this subsite") ), $domainTable, @@ -692,103 +655,98 @@ class Subsite extends DataObject 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 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) - ); + 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'); + $subsiteTabs->addExtraClass('subsite-model'); - $this->extend('updateCMSFields', $fields); - return $fields; - } + $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'); + /** + * + * @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 $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 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_PATH)) { + foreach(scandir(THEMES_PATH) as $theme) { + if($theme[0] == '.') {continue;} + $theme = strtok($theme,'_'); + $themes[$theme] = $theme; + } + ksort($themes); + } + return $themes; + } + } - /** - * - * @return ValidationResult - */ - public function validate() - { - $result = parent::validate(); - if (!$this->Title) { - $result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"')); - } - return $result; - } + /** + * @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 @@ -809,26 +767,27 @@ class Subsite extends DataObject */ 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']; + // Get best SubsiteDomain object + $domainObject = $this->getPrimarySubsiteDomain(); + if ($domainObject) { + return $domainObject->SubstitutedDomain; } + + // If there are no objects, default to the current hostname + return $_SERVER['HTTP_HOST']; + } + + /** + * Finds the primary {@see SubsiteDomain} object for this subsite + * + * @return SubsiteDomain + */ + public function getPrimarySubsiteDomain() + { + return $this + ->Domains() + ->sort('"IsPrimary" DESC') + ->first(); } /** @@ -841,49 +800,53 @@ class Subsite extends DataObject } /** - * + * Get the absolute URL for this subsite * @return string */ public function absoluteBaseURL() { - return "http://" . $this->domain() . Director::baseURL(); + // Get best SubsiteDomain object + $domainObject = $this->getPrimarySubsiteDomain(); + if ($domainObject) { + return $domainObject->absoluteBaseURL(); + } + + // Fall back to the current base url + return Director::absoluteBaseURL(); } - /** - * @todo getClassName is redundant, already stored as a database field? - */ - public function getClassName() - { - return $this->class; - } + /** + * @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)] - ); + /** + * 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 <<Some new content
'; + $mainHome->write(); + $this->assertEquals('home', $mainHome->URLSegment); + $mainHome->doPublish(); + $mainHomeLive = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $mainHome->ID)); + $this->assertEquals('home', $mainHomeLive->URLSegment); + + // Saving existing page in another subsite doesn't change urls + Subsite::changeSubsite($mainSubsiteID); + $subsite1Home = $this->objFromFixture('Page', 'subsite1_home'); + $subsite1Home->Content = 'In subsite 1
'; + $subsite1Home->write(); + $this->assertEquals('home', $subsite1Home->URLSegment); + $subsite1Home->doPublish(); + $subsite1HomeLive = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $subsite1Home->ID)); + $this->assertEquals('home', $subsite1HomeLive->URLSegment); + + // Creating a new page in a subsite doesn't conflict with urls in other subsites + $subsite1ID = $this->idFromFixture(Subsite::class, 'subsite1'); + Subsite::changeSubsite($subsite1ID); + $subsite1NewPage = new Page(); + $subsite1NewPage->SubsiteID = $subsite1ID; + $subsite1NewPage->Title = 'Important Page (Subsite 1)'; + $subsite1NewPage->URLSegment = 'important-page'; // Also exists in main subsite + $subsite1NewPage->write(); + $this->assertEquals('important-page', $subsite1NewPage->URLSegment); + $subsite1NewPage->doPublish(); + $subsite1NewPageLive = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $subsite1NewPage->ID)); + $this->assertEquals('important-page', $subsite1NewPageLive->URLSegment); + + // Creating a new page in a subsite DOES conflict with urls in the same subsite + $subsite1NewPage2 = new Page(); + $subsite1NewPage2->SubsiteID = $subsite1ID; + $subsite1NewPage2->Title = 'Important Page (Subsite 1)'; + $subsite1NewPage2->URLSegment = 'important-page'; // Also exists in main subsite + $subsite1NewPage2->write(); + $this->assertEquals('important-page-2', $subsite1NewPage2->URLSegment); + $subsite1NewPage2->doPublish(); + $subsite1NewPage2Live = Versioned::get_one_by_stage('Page', 'Live', sprintf('"SiteTree"."ID" = \'%d\'', $subsite1NewPage2->ID)); + $this->assertEquals('important-page-2', $subsite1NewPage2Live->URLSegment); + + // Original page is left un-modified + $mainSubsiteImportantPageID = $this->idFromFixture('Page', 'importantpage'); + $mainSubsiteImportantPage = Page::get()->byID($mainSubsiteImportantPageID); + $this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment); + $mainSubsiteImportantPage->Content = 'New Important Page Content
'; + $mainSubsiteImportantPage->write(); + $this->assertEquals('important-page', $mainSubsiteImportantPage->URLSegment); } + public function testCopySubsiteWithChildren() { + $page = $this->objFromFixture('Page', 'about'); + $newSubsite = $this->objFromFixture(Subsite::class, 'subsite1'); + + $moved = $page->duplicateToSubsite($newSubsite->ID, true); + $this->assertEquals($moved->SubsiteID, $newSubsite->ID, 'Ensure returned records are on new subsite'); + $this->assertEquals($moved->AllChildren()->count(), $page->AllChildren()->count(), 'All pages are copied across'); + } + + public function testCopySubsiteWithoutChildren() { + $page = $this->objFromFixture('Page', 'about'); + $newSubsite = $this->objFromFixture(Subsite::class, 'subsite2'); + + $moved = $page->duplicateToSubsite($newSubsite->ID, false); + $this->assertEquals($moved->SubsiteID, $newSubsite->ID, 'Ensure returned records are on new subsite'); + $this->assertEquals($moved->AllChildren()->count(), 0, 'All pages are copied across'); + } } + class SiteTreeSubsitesTest_ClassA extends SiteTree implements TestOnly { } - class SiteTreeSubsitesTest_ClassB extends SiteTree implements TestOnly { } diff --git a/tests/SubsiteAdminFunctionalTest.php b/tests/SubsiteAdminFunctionalTest.php index c4714e5..2409e94 100644 --- a/tests/SubsiteAdminFunctionalTest.php +++ b/tests/SubsiteAdminFunctionalTest.php @@ -1,23 +1,24 @@ get($url); while ($location = $response->getHeader('Location')) { @@ -25,13 +26,13 @@ class SubsiteAdminFunctionalTest extends FunctionalTest } echo $response->getHeader('Location'); - return $response; - } + return $response; + } /** * Anonymous user cannot access anything. */ - function testAnonymousIsForbiddenAdminAccess() + public function testAnonymousIsForbiddenAdminAccess() { $response = $this->getAndFollowAll('admin/pages/?SubsiteID=0'); $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed'); @@ -40,7 +41,7 @@ class SubsiteAdminFunctionalTest extends FunctionalTest $response = $this->getAndFollowAll("admin/pages/?SubsiteID={$subsite1->ID}"); $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'Admin is disallowed'); - $response = $this->getAndFollowAll(SubsiteXHRController::class); + $response = $this->getAndFollowAll('SubsiteXHRController'); $this->assertRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'SubsiteXHRController is disallowed'); } @@ -48,9 +49,9 @@ class SubsiteAdminFunctionalTest extends FunctionalTest /** * Admin should be able to access all subsites and the main site */ - function testAdminCanAccessAllSubsites() + public function testAdminCanAccessAllSubsites() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); + $member = $this->objFromFixture(Member::class, 'admin'); Session::set("loggedInAs", $member->ID); $this->getAndFollowAll('admin/pages/?SubsiteID=0'); @@ -67,59 +68,54 @@ class SubsiteAdminFunctionalTest extends FunctionalTest 'SubsiteXHRController is reachable'); } - function testAdminIsRedirectedToObjectsSubsite() + public function testAdminIsRedirectedToObjectsSubsite() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); + $member = $this->objFromFixture(Member::class, 'admin'); Session::set("loggedInAs", $member->ID); $mainSubsitePage = $this->objFromFixture('Page', 'mainSubsitePage'); $subsite1Home = $this->objFromFixture('Page', 'subsite1_home'); - Config::inst()->nest(); + Config::inst()->nest(); - Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', - false); + Config::inst()->update(CMSPageEditController::class, '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->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); + Config::inst()->update(CMSPageEditController::class, '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->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->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(); - } + 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() + public function testEditorCanAccessAllSubsites() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); + $member = $this->objFromFixture(Member::class, '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'); + $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); + $response = $this->getAndFollowAll('SubsiteXHRController'); $this->assertNotRegExp('#^Security/login.*#', $this->mainSession->lastUrl(), 'SubsiteXHRController is reachable'); } @@ -127,32 +123,32 @@ class SubsiteAdminFunctionalTest extends FunctionalTest /** * Test a member who only has access to one subsite (subsite1) and only some sections (pages and security). */ - function testSubsiteAdmin() + public function testSubsiteAdmin() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + $member = $this->objFromFixture(Member::class, '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 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 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 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 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); diff --git a/tests/SubsiteAdminTest.php b/tests/SubsiteAdminTest.php index 28c4604..ba04847 100644 --- a/tests/SubsiteAdminTest.php +++ b/tests/SubsiteAdminTest.php @@ -1,26 +1,26 @@ $this->idFromFixture('SilverStripe\\Security\\Member', 'admin') - ]); + return new Session(array( + 'loggedInAs' => $this->idFromFixture(Member::class, 'admin') + )); } /** * Test generation of the view */ - function testBasicView() + public function testBasicView() { Subsite::$write_hostmap = false; $subsite1ID = $this->objFromFixture(Subsite::class, 'domaintest1')->ID; @@ -41,9 +41,9 @@ class SubsiteAdminTest extends BaseSubsiteTest * 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() + public function testMainsiteAdminCanAccessAllSubsites() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); + $member = $this->objFromFixture(Member::class, 'admin'); Session::set("loggedInAs", $member->ID); $cmsMain = new CMSMain(); @@ -51,13 +51,11 @@ class SubsiteAdminTest extends BaseSubsiteTest $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"); - } + $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 348452d..898c44b 100644 --- a/tests/SubsiteTest.php +++ b/tests/SubsiteTest.php @@ -1,35 +1,53 @@ update(Director::class, 'alternate_base_url', '/'); $this->origStrictSubdomainMatching = Subsite::$strict_subdomain_matching; + $this->origServer = $_SERVER; Subsite::$strict_subdomain_matching = false; } - function tearDown() + public function tearDown() { - parent::tearDown(); - + $_SERVER = $this->origServer; Subsite::$strict_subdomain_matching = $this->origStrictSubdomainMatching; + + parent::tearDown(); } /** * Create a new subsite from the template and verify that all the template's pages are copied */ - function testSubsiteCreation() + public function testSubsiteCreation() { Subsite::$write_hostmap = false; @@ -38,11 +56,12 @@ class SubsiteTest extends BaseSubsiteTest // Test that changeSubsite is working Subsite::changeSubsite($template->ID); + $this->assertEquals($template->ID, Subsite::currentSubsiteID()); $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"); + $pages = DataObject::get(SiteTree::class); $totalPages = $pages->Count(); foreach ($pages as $page) { $this->assertEquals($template->ID, $page->SubsiteID); @@ -70,7 +89,7 @@ class SubsiteTest extends BaseSubsiteTest /** * Confirm that domain lookup is working */ - function testDomainLookup() + public function testDomainLookup() { // Clear existing fixtures foreach (DataObject::get(Subsite::class) as $subsite) { @@ -81,20 +100,20 @@ class SubsiteTest extends BaseSubsiteTest } // Much more expressive than YML in this case - $subsite1 = $this->createSubsiteWithDomains([ + $subsite1 = $this->createSubsiteWithDomains(array( 'one.example.org' => true, 'one.*' => false, - ]); - $subsite2 = $this->createSubsiteWithDomains([ + )); + $subsite2 = $this->createSubsiteWithDomains(array( 'two.mysite.com' => true, '*.mysite.com' => false, 'subdomain.onmultiplesubsites.com' => false, - ]); - $subsite3 = $this->createSubsiteWithDomains([ + )); + $subsite3 = $this->createSubsiteWithDomains(array( 'three.*' => true, // wildcards in primary domain are not recommended 'subdomain.unique.com' => false, '*.onmultiplesubsites.com' => false, - ]); + )); $this->assertEquals( $subsite3->ID, @@ -142,10 +161,9 @@ class SubsiteTest extends BaseSubsiteTest Subsite::getSubsiteIDForDomain('unknown.madeup.com'), "Doesn't match unknown subsite" ); - } - function testStrictSubdomainMatching() + public function testStrictSubdomainMatching() { // Clear existing fixtures foreach (DataObject::get(Subsite::class) as $subsite) { @@ -156,15 +174,15 @@ class SubsiteTest extends BaseSubsiteTest } // Much more expressive than YML in this case - $subsite1 = $this->createSubsiteWithDomains([ + $subsite1 = $this->createSubsiteWithDomains(array( 'example.org' => true, 'example.com' => false, '*.wildcard.com' => false, - ]); - $subsite2 = $this->createSubsiteWithDomains([ + )); + $subsite2 = $this->createSubsiteWithDomains(array( 'www.example.org' => true, 'www.wildcard.com' => false, - ]); + )); Subsite::$strict_subdomain_matching = false; @@ -216,21 +234,20 @@ class SubsiteTest extends BaseSubsiteTest $failed, 'Fails on multiple matches with strict checking and wildcard vs. www' ); - } protected function createSubsiteWithDomains($domains) { - $subsite = new Subsite([ + $subsite = new Subsite(array( 'Title' => 'My Subsite' - ]); + )); $subsite->write(); foreach ($domains as $domainStr => $isPrimary) { - $domain = new SubsiteDomain([ + $domain = new SubsiteDomain(array( 'Domain' => $domainStr, 'IsPrimary' => $isPrimary, 'SubsiteID' => $subsite->ID - ]); + )); $domain->write(); } @@ -240,7 +257,7 @@ class SubsiteTest extends BaseSubsiteTest /** * Test the Subsite->domain() method */ - function testDefaultDomain() + public function testDefaultDomain() { $this->assertEquals('one.example.org', $this->objFromFixture(Subsite::class, 'domaintest1')->domain()); @@ -248,8 +265,6 @@ class SubsiteTest extends BaseSubsiteTest $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()); @@ -259,109 +274,157 @@ class SubsiteTest extends BaseSubsiteTest $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; + $this->assertEquals('http://'.$_SERVER['HTTP_HOST'].Director::baseURL(), singleton(Subsite::class)->absoluteBaseURL()); } - function testAllSites() + /** + * Tests that Subsite and SubsiteDomain both respect http protocol correctly + */ + public function testDomainProtocol() { + // domaintest2 has 'protocol' + $subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2'); + $domain2a = $this->objFromFixture(SubsiteDomain::class, 'dt2a'); + $domain2b = $this->objFromFixture(SubsiteDomain::class, 'dt2b'); + + // domaintest4 is 'https' (primary only) + $subsite4 = $this->objFromFixture(Subsite::class, 'domaintest4'); + $domain4a = $this->objFromFixture(SubsiteDomain::class, 'dt4a'); + $domain4b = $this->objFromFixture(SubsiteDomain::class, 'dt4b'); // secondary domain is http only though + + // domaintest5 is 'http' + $subsite5 = $this->objFromFixture(Subsite::class, 'domaintest5'); + $domain5a = $this->objFromFixture(SubsiteDomain::class, 'dt5'); + + // Check protocol when current protocol is http:// + $_SERVER['HTTP_HOST'] = 'www.mysite.com'; + $_SERVER['HTTPS'] = ''; + + $this->assertEquals('http://two.mysite.com/', $subsite2->absoluteBaseURL()); + $this->assertEquals('http://two.mysite.com/', $domain2a->absoluteBaseURL()); + $this->assertEquals('http://subsite.mysite.com/', $domain2b->absoluteBaseURL()); + $this->assertEquals('https://www.primary.com/', $subsite4->absoluteBaseURL()); + $this->assertEquals('https://www.primary.com/', $domain4a->absoluteBaseURL()); + $this->assertEquals('http://www.secondary.com/', $domain4b->absoluteBaseURL()); + $this->assertEquals('http://www.tertiary.com/', $subsite5->absoluteBaseURL()); + $this->assertEquals('http://www.tertiary.com/', $domain5a->absoluteBaseURL()); + + // Check protocol when current protocol is https:// + $_SERVER['HTTP_HOST'] = 'www.mysite.com'; + $_SERVER['HTTPS'] = 'ON'; + + $this->assertEquals('https://two.mysite.com/', $subsite2->absoluteBaseURL()); + $this->assertEquals('https://two.mysite.com/', $domain2a->absoluteBaseURL()); + $this->assertEquals('https://subsite.mysite.com/', $domain2b->absoluteBaseURL()); + $this->assertEquals('https://www.primary.com/', $subsite4->absoluteBaseURL()); + $this->assertEquals('https://www.primary.com/', $domain4a->absoluteBaseURL()); + $this->assertEquals('http://www.secondary.com/', $domain4b->absoluteBaseURL()); + $this->assertEquals('http://www.tertiary.com/', $subsite5->absoluteBaseURL()); + $this->assertEquals('http://www.tertiary.com/', $domain5a->absoluteBaseURL()); + } + + public 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'); + $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'), + array('Title' => 'Test Non-SSL'), + array('Title' => 'Test SSL') + ), $subsites, 'Lists all subsites'); } - function testAllAccessibleSites() + public function testAllAccessibleSites() { - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + $member = $this->objFromFixture(Member::class, 'subsite1member'); $subsites = Subsite::all_accessible_sites(true, 'Main site', $member); - $this->assertDOSEquals([ - ['Title' => 'Subsite1 Template'] - ], $subsites, 'Lists member-accessible sites.'); + $this->assertDOSEquals(array( + array('Title' =>'Subsite1 Template') + ), $subsites, 'Lists member-accessible sites.'); } /** * Test Subsite::accessible_sites() */ - function testAccessibleSites() + public function testAccessibleSites() { $member1Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, - $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member')); + $this->objFromFixture(Member::class, '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')); + $this->objFromFixture(Member::class, 'admin')); $adminSiteTitles = $adminSites->column("Title"); sort($adminSiteTitles); - $this->assertEquals([ + $this->assertEquals(array( 'Subsite1 Template', 'Subsite2 Template', 'Template', 'Test 1', 'Test 2', 'Test 3', - ], $adminSiteTitles); + 'Test Non-SSL', + 'Test SSL' + ), array_values($adminSiteTitles)); - $member2Sites = Subsite::accessible_sites("CMS_ACCESS_CMSMain", false, null, - $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member2')); + $member2Sites = Subsite::accessible_sites( + "CMS_ACCESS_CMSMain", false, null, + $this->objFromFixture(Member::class, 'subsite1member2') + ); $member2SiteTitles = $member2Sites->column("Title"); sort($member2SiteTitles); $this->assertEquals('Subsite1 Template', $member2SiteTitles[0], 'Member can get to subsite via a group role'); } - function testhasMainSitePermission() + public 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'); + $admin = $this->objFromFixture(Member::class, 'admin'); + $subsite1member = $this->objFromFixture(Member::class, 'subsite1member'); + $subsite1admin = $this->objFromFixture(Member::class, 'subsite1admin'); + $allsubsitesauthor = $this->objFromFixture(Member::class, 'allsubsitesauthor'); $this->assertTrue( Subsite::hasMainSitePermission($admin), 'Default permissions granted for super-admin' ); $this->assertTrue( - Subsite::hasMainSitePermission($admin, ["ADMIN"]), + Subsite::hasMainSitePermission($admin, array("ADMIN")), 'ADMIN permissions granted for super-admin' ); $this->assertFalse( - Subsite::hasMainSitePermission($subsite1admin, ["ADMIN"]), + Subsite::hasMainSitePermission($subsite1admin, array("ADMIN")), 'ADMIN permissions (on main site) denied for subsite1 admin' ); $this->assertFalse( - Subsite::hasMainSitePermission($subsite1admin, ["CMS_ACCESS_CMSMain"]), + Subsite::hasMainSitePermission($subsite1admin, array("CMS_ACCESS_CMSMain")), 'CMS_ACCESS_CMSMain (on main site) denied for subsite1 admin' ); $this->assertFalse( - Subsite::hasMainSitePermission($allsubsitesauthor, ["ADMIN"]), + 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, ["CMS_ACCESS_CMSMain"]), + 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, ["ADMIN"]), + Subsite::hasMainSitePermission($subsite1member, array("ADMIN")), 'ADMIN (on main site) denied for subsite1 subsite1 cms author' ); $this->assertFalse( - Subsite::hasMainSitePermission($subsite1member, ["CMS_ACCESS_CMSMain"]), + Subsite::hasMainSitePermission($subsite1member, array("CMS_ACCESS_CMSMain")), 'CMS_ACCESS_CMSMain (on main site) denied for subsite1 cms author' ); } - function testDuplicateSubsite() + public function testDuplicateSubsite() { // get subsite1 & create page $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); diff --git a/tests/SubsiteTest.yml b/tests/SubsiteTest.yml index 4cecc2a..d681128 100644 --- a/tests/SubsiteTest.yml +++ b/tests/SubsiteTest.yml @@ -1,79 +1,103 @@ SilverStripe\Subsites\Model\Subsite: main: - Title: 'Template' + Title: Template subsite1: - Title: 'Subsite1 Template' + Title: Subsite1 Template subsite2: - Title: 'Subsite2 Template' + Title: Subsite2 Template domaintest1: - Title: 'Test 1' + Title: Test 1 domaintest2: - Title: 'Test 2' + Title: Test 2 domaintest3: - Title: 'Test 3' + Title: Test 3 + domaintest4: + Title: 'Test SSL' + domaintest5: + Title: 'Test Non-SSL' SilverStripe\Subsites\Model\SubsiteDomain: subsite1: SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1 - Domain: 'subsite1.*' + Domain: subsite1.* + Protocol: automatic subsite2: SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2 - Domain: 'subsite2.*' + Domain: subsite2.* + Protocol: automatic dt1a: SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest1 - Domain: 'one.example.org' + Domain: one.example.org + Protocol: automatic IsPrimary: 1 dt1b: SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest1 - Domain: 'one.*' + Domain: one.* + Protocol: automatic dt2a: SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest2 - Domain: 'two.mysite.com' + Domain: two.mysite.com + Protocol: automatic IsPrimary: 1 dt2b: SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest2 - Domain: '*.mysite.com' + Domain: *.mysite.com + Protocol: automatic dt3: SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest3 - Domain: 'three.*' + Domain: three.* + Protocol: automatic + IsPrimary: 1 + dt4a: + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest4 + Domain: www.primary.com + Protocol: https + dt4b: + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest4 + Domain: www.secondary.com + Protocol: http + dt5: + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.domaintest5 + Domain: www.tertiary.com + Protocol: http IsPrimary: 1 Page: - mainSubsitePage: - Title: 'MainSubsitePage' - SubsiteID: 0 - home: - Title: 'Home' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main - about: - Title: 'About' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main - linky: - Title: 'Linky' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main - staff: - Title: 'Staff' - ParentID: =>Page.about - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main - contact: - Title: 'Contact Us' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main - importantpage: - Title: 'Important Page' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main - subsite1_home: - Title: 'Home (Subsite 1)' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1 - subsite1_contactus: - Title: 'Contact Us (Subsite 1)' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1 - subsite1_staff: - Title: 'Staff' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1 - subsite2_home: - Title: 'Home (Subsite 2)' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2 - subsite2_contactus: - Title: 'Contact Us (Subsite 2)' - SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2 + mainSubsitePage: + Title: 'MainSubsitePage' + SubsiteID: 0 + URLSegment: mainsubsitepagehome: + Title: 'Home' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main + URLSegment: homeabout: + Title: 'About' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main + URLSegment: aboutlinky: + Title: 'Linky' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main + URLSegment: linkystaff: + Title: 'Staff' + ParentID: =>Page.about + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main + URLSegment: staffcontact: + Title: 'Contact Us' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main + URLSegment: contact-usimportantpage: + Title: 'Important Page' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.main + URLSegment: important-pagesubsite1_home: + Title: 'Home (Subsite 1)' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1 + URLSegment: homesubsite1_contactus: + Title: 'Contact Us (Subsite 1)' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1 + URLSegment: contact-ussubsite1_staff: + Title: 'Staff' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite1 + URLSegment: staffsubsite2_home: + Title: 'Home (Subsite 2)' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2 + URLSegment: homesubsite2_contactus: + Title: 'Contact Us (Subsite 2)' + SubsiteID: =>SilverStripe\Subsites\Model\Subsite.subsite2URLSegment: contact-us SilverStripe\Security\PermissionRoleCode: roleCode1: diff --git a/tests/SubsiteXHRControllerTest.php b/tests/SubsiteXHRControllerTest.php new file mode 100644 index 0000000..5303fa7 --- /dev/null +++ b/tests/SubsiteXHRControllerTest.php @@ -0,0 +1,47 @@ +get('SubsiteXHRController', null, array( + 'X-Pjax' => 'SubsiteList', + 'X-Requested-With' => 'XMLHttpRequest' + )); + $this->assertEquals(403, $result->getStatusCode()); + + // Login with NO permissions + $this->logInWithPermission('NOT_CMS_PERMISSION'); + $result = $this->get('SubsiteXHRController', null, array( + 'X-Pjax' => 'SubsiteList', + 'X-Requested-With' => 'XMLHttpRequest' + )); + $this->assertEquals(403, $result->getStatusCode()); + + // Test cms user + $this->logInWithPermission('CMS_ACCESS_CMSMain'); + $result = $this->get('SubsiteXHRController', null, array( + 'X-Pjax' => 'SubsiteList', + 'X-Requested-With' => 'XMLHttpRequest' + )); + $this->assertEquals(200, $result->getStatusCode()); + $this->assertEquals('text/json', $result->getHeader('Content-Type')); + $body = $result->getBody(); + $this->assertContains('Main site', $body); + $this->assertContains('Test 1', $body); + $this->assertContains('Test 2', $body); + $this->assertContains('Test 3', $body); + } +} diff --git a/tests/SubsitesVirtualPageTest.php b/tests/SubsitesVirtualPageTest.php index dda8a87..e247a2c 100644 --- a/tests/SubsitesVirtualPageTest.php +++ b/tests/SubsitesVirtualPageTest.php @@ -2,19 +2,25 @@ use SilverStripe\Assets\Filesystem; use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore; -use SilverStripe\Core\Config\Config; +use SilverStripe\Control\Director; +use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\Assets\File; use SilverStripe\ORM\DB; +use SilverStripe\Core\Config\Config; +use SilverStripe\Versioned\Versioned; use SilverStripe\Subsites\Model\Subsite; use SilverStripe\Subsites\Pages\SubsitesVirtualPage; -use SilverStripe\Versioned\Versioned; - class SubsitesVirtualPageTest extends BaseSubsiteTest { - static $fixture_file = [ + public static $fixture_file = array( 'subsites/tests/SubsiteTest.yml', 'subsites/tests/SubsitesVirtualPageTest.yml', - ]; + ); + + protected $illegalExtensions = array( + 'SiteTree' => array('Translatable') + ); public function setUp() { @@ -24,8 +30,8 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest 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'); + $file = $this->objFromFixture(File::class, 'file1'); + $page = $this->objFromFixture(SiteTree::class, 'page1'); $fromPath = __DIR__ . '/testscript-test-file.pdf'; $destPath = TestAssetStore::getLocalPath($file); Filesystem::makeFolder(dirname($destPath)); @@ -65,13 +71,14 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $this->assertEquals($svp->Title, $linky->Title); } - function testFileLinkRewritingOnVirtualPages() + public function testFileLinkRewritingOnVirtualPages() { // File setup $this->logInWithPermission('ADMIN'); + touch(Director::baseFolder() . '/assets/testscript-test-file.pdf'); // Publish the source page - $page = $this->objFromFixture('SilverStripe\\CMS\\Model\\SiteTree', 'page1'); + $page = $this->objFromFixture(SiteTree::class, 'page1'); $this->assertTrue($page->doPublish()); // Create a virtual page from it, and publish that @@ -81,7 +88,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $svp->doPublish(); // Rename the file - $file = $this->objFromFixture('SilverStripe\\Assets\\File', 'file1'); + $file = $this->objFromFixture(File::class, 'file1'); $file->Name = 'renamed-test-file.pdf'; $file->write(); @@ -92,7 +99,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest DB::query("SELECT \"Content\" FROM \"SiteTree_Live\" WHERE \"ID\" = $svp->ID")->value()); } - function testSubsiteVirtualPagesArentInappropriatelyPublished() + public function testSubsiteVirtualPagesArentInappropriatelyPublished() { // Fixture $p = new Page(); @@ -151,7 +158,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest * 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() + public function testPublishedSubsiteVirtualPagesUpdateIfTargetPageUpdates() { // create page $p = new Page(); @@ -193,9 +200,9 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $this->assertEquals($svpdb->Title, $p->Title); } - function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() + public function testUnpublishingParentPageUnpublishesSubsiteVirtualPages() { - Config::modify()->set('StaticPublisher', 'disable_realtime', true); + Config::inst()->update('StaticPublisher', 'disable_realtime', true); // Go to main site, get parent page $subsite = $this->objFromFixture(Subsite::class, 'main'); @@ -224,14 +231,12 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $page->doUnpublish(); Subsite::changeSubsite($vp1->SubsiteID); - $onLive = Versioned::get_one_by_stage(SubsitesVirtualPage::class, 'Live', - "\"SiteTree_Live\".\"ID\" = " . $vp1->ID); + $onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', '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); + $onLive = Versioned::get_one_by_stage('SubsitesVirtualPage', 'Live', "\"SiteTree_Live\".\"ID\" = ".$vp2->ID); $this->assertNull($onLive, 'SVP has been removed from live'); } @@ -239,7 +244,7 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest * Similar to {@link SiteTreeSubsitesTest->testTwoPagesWithSameURLOnDifferentSubsites()} * and {@link SiteTreeSubsitesTest->testPagesInDifferentSubsitesCanShareURLSegment()}. */ - function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() + public function testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite() { Subsite::$write_hostmap = false; $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); @@ -274,16 +279,25 @@ class SubsitesVirtualPageTest extends BaseSubsiteTest $subsite2Vp->URLSegment, $subsite1Page->URLSegment, "Does allow explicit URLSegment overrides when only existing in a different subsite" + ); + + // When changing subsites and re-saving this page, it doesn't trigger a change + Subsite::changeSubsite($subsite1->ID); + $subsite1Page->write(); + $subsite2Vp->write(); + $this->assertEquals( + $subsite2Vp->URLSegment, + $subsite1Page->URLSegment, + "SubsiteVirtualPage doesn't change urls when being written in another subsite" ); } - function fixVersionNumberCache($page) + public 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]); + Versioned::prepopulate_versionnumber_cache(SiteTree::class, 'Stage', array($p->ID)); + Versioned::prepopulate_versionnumber_cache(SiteTree::class, 'Live', array($p->ID)); } } - } diff --git a/tests/WildcardDomainFieldTest.php b/tests/WildcardDomainFieldTest.php new file mode 100644 index 0000000..5bb951a --- /dev/null +++ b/tests/WildcardDomainFieldTest.php @@ -0,0 +1,74 @@ +assertTrue($field->checkHostname($domain), "Validate that {$domain} is a valid domain name"); + } + + /** + * Check that valid domains are accepted + * + * @dataProvider invalidDomains + */ + public function testInvalidDomains($domain) { + $field = new WildcardDomainField('DomainField'); + $this->assertFalse($field->checkHostname($domain), "Validate that {$domain} is an invalid domain name"); + } + + /** + * Check that valid domains are accepted + * + * @dataProvider validWildcards + */ + public function testValidWildcards($domain) { + $field = new WildcardDomainField('DomainField'); + $this->assertTrue($field->checkHostname($domain), "Validate that {$domain} is a valid domain wildcard"); + } + + public function validDomains() { + return array( + array('www.mysite.com'), + array('domain7'), + array('mysite.co.n-z'), + array('subdomain.my-site.com'), + array('subdomain.mysite') + ); + } + + public function invalidDomains() { + return array( + array('-mysite'), + array('.mysite'), + array('mys..ite'), + array('mysite-'), + array('mysite.'), + array('-mysite.*'), + array('.mysite.*'), + array('mys..ite.*'), + array('*.mysite-'), + array('*.mysite.') + ); + } + + public function validWildcards() { + return array( + array('*.mysite.com'), + array('mys*ite.com'), + array('*.my-site.*'), + array('*') + ); + } + +} diff --git a/tests/behat/features/bootstrap/Context/FeatureContext.php b/tests/behat/features/bootstrap/Context/FeatureContext.php index ff25046..77db1dd 100644 --- a/tests/behat/features/bootstrap/Context/FeatureContext.php +++ b/tests/behat/features/bootstrap/Context/FeatureContext.php @@ -6,15 +6,20 @@ 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\BehatExtension\Context\BasicContext; +use SilverStripe\BehatExtension\Context\LoginContext; +use SilverStripe\BehatExtension\Context\FixtureContext; +use SilverStripe\Framework\Test\Behaviour\CmsFormsContext; +use SilverStripe\Framework\Test\Behaviour\CmsUiContext;l +use SilverStripe\Cms\Test\Behaviour; +use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Injector\Injector; -use SilverStripe\Framework\Test\Behaviour\CmsFormsContext; -use SilverStripe\Framework\Test\Behaviour\CmsUiContext; - +use SilverStripe\Dev\BehatFixtureFactory; +use SilverStripe\Dev\FixtureBlueprint; +use SilverStripe\Dev\FixtureFactory; +use SilverStripe\Security\Member; // PHPUnit require_once 'PHPUnit/Autoload.php'; @@ -27,8 +32,7 @@ require_once 'PHPUnit/Framework/Assert/Functions.php'; * Uses subcontexts to extend functionality. */ class FeatureContext extends SilverStripeContext -{ - + { /** * @var FixtureFactory */ @@ -55,17 +59,17 @@ class FeatureContext extends SilverStripeContext // Use blueprints to set user name from identifier $factory = $fixtureContext->getFixtureFactory(); - $blueprint = Injector::inst()->create('SilverStripe\\Dev\\FixtureBlueprint', 'SilverStripe\\Security\\Member'); + $blueprint = Injector::inst()->create(FixtureBlueprint::class, Member::class); $blueprint->addCallback('beforeCreate', function ($identifier, &$data, &$fixtures) { if (!isset($data['FirstName'])) { $data['FirstName'] = $identifier; } }); - $factory->define('SilverStripe\\Security\\Member', $blueprint); + $factory->define(Member::class, $blueprint); // Auto-publish pages - foreach (ClassInfo::subclassesFor('SilverStripe\\CMS\\Model\\SiteTree') as $id => $class) { - $blueprint = Injector::inst()->create('SilverStripe\\Dev\\FixtureBlueprint', $class); + foreach (ClassInfo::subclassesFor(SiteTree::class) as $id => $class) { + $blueprint = Injector::inst()->create(FixtureBlueprint::class, $class); $blueprint->addCallback('afterCreate', function ($obj, $identifier, &$data, &$fixtures) { $obj->publish('Stage', 'Live'); }); @@ -88,7 +92,7 @@ class FeatureContext extends SilverStripeContext public function getFixtureFactory() { if (!$this->fixtureFactory) { - $this->fixtureFactory = Injector::inst()->create('SilverStripe\\Dev\\BehatFixtureFactory'); + $this->fixtureFactory = Injector::inst()->create(BehatFixtureFactory::class); } return $this->fixtureFactory;