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', - '

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

' - )); - } - } - } + if($this->owner instanceof Folder) { + $sites = Subsite::accessible_sites('CMS_ACCESS_AssetAdmin'); + $values = []; + $values[0] = _t('FileSubsites.AllSitesDropdownOpt','All sites'); + foreach ($sites as $site) { + $values[$site->ID] = $site->Title; + } + ksort($values); + if($sites){ + //Dropdown needed to move folders between subsites + $dropdown = new DropdownField( + 'SubsiteID', + _t('FileSubsites.SubsiteFieldLabel',Subsite::class), + $values + ); + $dropdown->addExtraClass('subsites-move-dropdown'); + $fields->push($dropdown); + $fields->push(new LiteralField( + 'Message', + '

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

' + )); + } + } + } /** * Update any requests to limit the results to the current site @@ -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= to the request. - * - * @todo Pass $request object from controller so we don't have to rely on $_GET - * - * @param boolean $cache - * @return int ID of the current subsite instance - */ - public static function currentSubsiteID() - { - $id = NULL; + /** + * This function gets the current subsite ID from the session. It used in the backend so Ajax requests + * use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain + * directly from ModelAsController::getNestedController. Only gets Subsite instances which have their + * {@link IsPublic} flag set to TRUE. + * + * You can simulate subsite access without creating virtual hosts by appending ?SubsiteID= to the request. + * + * @todo Pass $request object from controller so we don't have to rely on $_GET + * + * @return int ID of the current subsite instance + */ + public static function currentSubsiteID() + {$id = null; if (isset($_GET['SubsiteID'])) { $id = (int)$_GET['SubsiteID']; - } else { - if (Subsite::$use_session_subsiteid) { - $id = Session::get('SubsiteID'); - } + } elseif (Subsite::$use_session_subsiteid) { + $id = Session::get('SubsiteID'); } - if ($id === NULL) { - $id = self::getSubsiteIDForDomain(); - } + if($id === null) { + $id = self::getSubsiteIDForDomain(); + } - return (int)$id; - } + return (int)$id; + } - /** - * Switch to another subsite through storing the subsite identifier in the current PHP session. - * Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE. - * - * @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself - */ - public static function changeSubsite($subsite) - { - // Session subsite change only meaningful if the session is active. - // Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID. - if (!Subsite::$use_session_subsiteid) { - return; - } + /** + * Switch to another subsite through storing the subsite identifier in the current PHP session. + * Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE. + * + * @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself + */ + public static function changeSubsite($subsite) { + // Session subsite change only meaningful if the session is active. + // Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID. + if (!Subsite::$use_session_subsiteid) return; - if (is_object($subsite)) { - $subsiteID = $subsite->ID; - } else { - $subsiteID = $subsite; - } + if(is_object($subsite)) $subsiteID = $subsite->ID; + else $subsiteID = $subsite; - Session::set('SubsiteID', (int)$subsiteID); + Session::set('SubsiteID', (int)$subsiteID); - // Set locale - if (is_object($subsite) && $subsite->Language != '') { - $locale = i18n::get_locale_from_lang($subsite->Language); - if ($locale) { - i18n::set_locale($locale); - } - } + // Set locale + if (is_object($subsite) && $subsite->Language != '') { + $locale = i18n::get_locale_from_lang($subsite->Language); + if($locale) { + i18n::set_locale($locale); + } + } - Permission::flush_permission_cache(); - } + Permission::flush_permission_cache(); + } - /** - * Get a matching subsite for the given host, or for the current HTTP_HOST. - * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string, - * for example matching all subdomains on *.example.com with one subsite, - * and all subdomains on *.example.org on another. - * - * @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. - * @return int Subsite ID - */ - public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) - { - if ($host == null && isset($_SERVER['HTTP_HOST'])) { - $host = $_SERVER['HTTP_HOST']; - } + /** + * Get a matching subsite for the given host, or for the current HTTP_HOST. + * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string, + * for example matching all subdomains on *.example.com with one subsite, + * and all subdomains on *.example.org on another. + * + * @param $host The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. + * @return int Subsite ID + */ + public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) { + if($host == null && isset($_SERVER['HTTP_HOST'])) { + $host = $_SERVER['HTTP_HOST']; + } - $matchingDomains = null; - $cacheKey = null; - if ($host) { - if (!self::$strict_subdomain_matching) { - $host = preg_replace('/^www\./', '', $host); - } + $matchingDomains = null; + $cacheKey = null; + if ($host) { + if(!self::$strict_subdomain_matching) $host = preg_replace('/^www\./', '', $host); $cacheKey = implode('_', [$host, Member::currentUserID(), self::$check_is_public]); if (isset(self::$_cache_subsite_for_domain[$cacheKey])) { return self::$_cache_subsite_for_domain[$cacheKey]; } - $SQL_host = Convert::raw2sql($host); - $matchingDomains = DataObject::get( - SubsiteDomain::class, - "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')", - "\"IsPrimary\" DESC" - )->innerJoin('Subsite', - "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1"); - } + $SQL_host = Convert::raw2sql($host); + $matchingDomains = DataObject::get( + SubsiteDomain::class, + "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')", + "\"IsPrimary\" DESC" + )->innerJoin('Subsite', "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1"); + } - if ($matchingDomains && $matchingDomains->Count()) { - $subsiteIDs = array_unique($matchingDomains->column('SubsiteID')); - $subsiteDomains = array_unique($matchingDomains->column('Domain')); - if (sizeof($subsiteIDs) > 1) { - throw new UnexpectedValueException(sprintf( - "Multiple subsites match on '%s': %s", - $host, - implode(',', $subsiteDomains) - )); - } + if($matchingDomains && $matchingDomains->Count()) { + $subsiteIDs = array_unique($matchingDomains->column('SubsiteID')); + $subsiteDomains = array_unique($matchingDomains->column('Domain')); + if(sizeof($subsiteIDs) > 1) { + throw new UnexpectedValueException(sprintf( + "Multiple subsites match on '%s': %s", + $host, + implode(',', $subsiteDomains) + )); + } - $subsiteID = $subsiteIDs[0]; - } else { - if ($default = DataObject::get_one(Subsite::class, "\"DefaultSite\" = 1")) { - // Check for a 'default' subsite - $subsiteID = $default->ID; - } else { - // Default subsite id = 0, the main site - $subsiteID = 0; - } - } + $subsiteID = $subsiteIDs[0]; + } else { + if($default = DataObject::get_one(Subsite::class, "\"DefaultSite\" = 1")) { + // Check for a 'default' subsite + $subsiteID = $default->ID; + } else { + // Default subsite id = 0, the main site + $subsiteID = 0; + }} - if ($cacheKey) { - self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID; - } + if ($cacheKey) { + self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID; + } - return $subsiteID; - } + return $subsiteID; + } - /** - * - * @param string $className - * @param string $filter - * @param string $sort - * @param string $join - * @param string $limit - * @return DataList - */ - public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") - { - $result = DataObject::get($className, $filter, $sort, $join, $limit); - $result = $result->setDataQueryParam('Subsite.filter', false); - return $result; - } + /** + * + * @param string $className + * @param string $filter + * @param string $sort + * @param string $join + * @param string $limit + * @return DataList + */ + public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "") { + $result = DataObject::get($className, $filter, $sort, $join, $limit); + $result = $result->setDataQueryParam('Subsite.filter', false); + return $result; + } /** * Disable the sub-site filtering; queries will select from all subsites @@ -307,18 +290,18 @@ class Subsite extends DataObject { $subsites = Subsite::get(); - if ($includeMainSite) { - $subsites = $subsites->toArray(); + if($includeMainSite) { + $subsites = $subsites->toArray(); - $mainSite = new Subsite(); - $mainSite->Title = $mainSiteTitle; - array_unshift($subsites, $mainSite); + $mainSite = new Subsite(); + $mainSite->Title = $mainSiteTitle; + array_unshift($subsites, $mainSite); - $subsites = ArrayList::create($subsites); - } + $subsites = ArrayList::create($subsites); + } - return $subsites; - } + return $subsites; + } /* * Returns an ArrayList of the subsites accessible to the current user. @@ -336,30 +319,30 @@ class Subsite extends DataObject return new ArrayList(); } if (!is_object($member)) { - $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); + $member = DataObject::get_by_id(Member::class, $member); } - $subsites = new ArrayList(); + $subsites = new ArrayList(); - // Collect subsites for all sections. - $menu = CMSMenu::get_viewable_menu_items(); - foreach ($menu as $candidate) { - if ($candidate->controller) { - $accessibleSites = singleton($candidate->controller)->sectionSites( - $includeMainSite, - $mainSiteTitle, - $member - ); + // Collect subsites for all sections. + $menu = CMSMenu::get_viewable_menu_items(); + foreach($menu as $candidate) { + if ($candidate->controller) { + $accessibleSites = singleton($candidate->controller)->sectionSites( + $includeMainSite, + $mainSiteTitle, + $member + ); - // Replace existing keys so no one site appears twice. - $subsites->merge($accessibleSites); - } - } + // Replace existing keys so no one site appears twice. + $subsites->merge($accessibleSites); + } + } - $subsites->removeDuplicates(); + $subsites->removeDuplicates(); - return $subsites; - } + return $subsites; + } /** * Return the subsites that the current user can access by given permission. @@ -371,12 +354,7 @@ class Subsite extends DataObject * @param $member * @return DataList of {@link Subsite} instances */ - public static function accessible_sites( - $permCode, - $includeMainSite = true, - $mainSiteTitle = "Main site", - $member = null - ) + public static function accessible_sites($permCode, $includeMainSite = true, $mainSiteTitle = "Main site", $member = null) { // Rationalise member arguments if (!$member) { @@ -386,21 +364,18 @@ class Subsite extends DataObject return new ArrayList(); } if (!is_object($member)) { - $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $member); + $member = DataObject::get_by_id(Member::class, $member); } - // Rationalise permCode argument - if (is_array($permCode)) { - $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'"; - } else { - $SQL_codes = "'" . Convert::raw2sql($permCode) . "'"; - } + // Rationalise permCode argument + if(is_array($permCode)) $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'"; + else $SQL_codes = "'" . Convert::raw2sql($permCode) . "'"; - // Cache handling - $cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle; - if (isset(self::$_cache_accessible_sites[$cacheKey])) { - return self::$_cache_accessible_sites[$cacheKey]; - } + // Cache handling + $cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle; + if(isset(self::$_cache_accessible_sites[$cacheKey])) { + return self::$_cache_accessible_sites[$cacheKey]; + } $subsites = DataList::create(Subsite::class) ->where("\"Subsite\".\"Title\" != ''") @@ -412,9 +387,7 @@ class Subsite extends DataObject ->innerJoin('Permission', "\"Group\".\"ID\"=\"Permission\".\"GroupID\" AND \"Permission\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"); - if (!$subsites) { - $subsites = new ArrayList(); - } + if(!$subsites) $subsites = new ArrayList(); $rolesSubsites = DataList::create(Subsite::class) ->where("\"Subsite\".\"Title\" != ''") @@ -428,19 +401,15 @@ class Subsite extends DataObject ->innerJoin('PermissionRoleCode', "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" AND \"PermissionRoleCode\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')"); - if (!$subsites && $rolesSubsites) { - return $rolesSubsites; - } + if(!$subsites && $rolesSubsites) return $rolesSubsites; - $subsites = new ArrayList($subsites->toArray()); + $subsites = new ArrayList($subsites->toArray()); - if ($rolesSubsites) { - foreach ($rolesSubsites as $subsite) { - if (!$subsites->find('ID', $subsite->ID)) { - $subsites->push($subsite); - } - } - } + if($rolesSubsites) foreach($rolesSubsites as $subsite) { + if(!$subsites->find('ID', $subsite->ID)) { + $subsites->push($subsite); + } + } if ($includeMainSite) { if (!is_array($permCode)) { @@ -528,24 +497,20 @@ class Subsite extends DataObject user_error('Permissions must be passed to Subsite::hasMainSitePermission as an array', E_USER_ERROR); } - if (!$member && $member !== FALSE) { + if (!$member && $member !== false) { $member = Member::currentUser(); } - if (!$member) { - return false; - } + if(!$member) return false; - if (!in_array("ADMIN", $permissionCodes)) { - $permissionCodes[] = "ADMIN"; - } + if(!in_array("ADMIN", $permissionCodes)) $permissionCodes[] = "ADMIN"; - $SQLa_perm = Convert::raw2sql($permissionCodes); - $SQL_perms = join("','", $SQLa_perm); - $memberID = (int)$member->ID; + $SQLa_perm = Convert::raw2sql($permissionCodes); + $SQL_perms = join("','", $SQLa_perm); + $memberID = (int)$member->ID; - // Count this user's groups which can access the main site - $groupCount = DB::query(" + // Count this user's groups which can access the main site + $groupCount = DB::query(" SELECT COUNT(\"Permission\".\"ID\") FROM \"Permission\" INNER JOIN \"Group\" ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1 @@ -554,8 +519,8 @@ class Subsite extends DataObject AND \"MemberID\" = {$memberID} ")->value(); - // Count this user's groups which have a role that can access the main site - $roleCount = DB::query(" + // Count this user's groups which have a role that can access the main site + $roleCount = DB::query(" SELECT COUNT(\"PermissionRoleCode\".\"ID\") FROM \"Group\" INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\" @@ -582,13 +547,13 @@ class Subsite extends DataObject 'Theme' => 'Varchar', 'Language' => 'Varchar(6)', - // Used to hide unfinished/private subsites from public view. - // If unset, will default to true - 'IsPublic' => 'Boolean', + // Used to hide unfinished/private subsites from public view. + // If unset, will default to true + 'IsPublic' => 'Boolean', - // Comma-separated list of disallowed page types - 'PageTypeBlacklist' => 'Text', - ]; + // Comma-separated list of disallowed page types + 'PageTypeBlacklist' => 'Text', + ]; /** * @@ -603,41 +568,40 @@ class Subsite extends DataObject * @var array */ private static $belongs_many_many = [ - "Groups" => "SilverStripe\\Security\\Group", + "Groups" => Group::class, ]; - /** - * - * @var array - */ - private static $defaults = [ - 'IsPublic' => 1 - ]; + /** + * + * @var array + */ + private static $defaults = [ + 'IsPublic' => 1 + ]; - /** - * - * @var array - */ - private static $searchable_fields = [ - 'Title', - 'Domains.Domain', - 'IsPublic', - ]; + /** + * + * @var array + */ + private static $searchable_fields = [ + 'Title', + 'Domains.Domain', + 'IsPublic', + ]; - /** - * - * @var string - */ - private static $default_sort = "\"Title\" ASC"; + /** + * + * @var string + */ + private static $default_sort = "\"Title\" ASC"; - /** - * @todo Possible security issue, don't grant edit permissions to everybody. - * @return boolean - */ - public function canEdit($member = false) - { - return true; - } + /** + * @todo Possible security issue, don't grant edit permissions to everybody. + * @return boolean + */ + public function canEdit($member = false) + {return true; + } /** * Show the configuration fields for each subsite @@ -646,7 +610,7 @@ class Subsite extends DataObject */ public function getCMSFields() { - if ($this->ID != 0) { + if ($this->ID!=0) { $domainTable = new GridField( "Domains", _t('Subsite.DomainsListTitle', "Domains"), @@ -656,18 +620,17 @@ class Subsite extends DataObject } else { $domainTable = new LiteralField( 'Domains', - '

' . _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 <<ID'); JS; - } + } - /** - * Make this subsite the current one - */ - public function activate() - { - Subsite::changeSubsite($this); - } + /** + * Make this subsite the current one + */ + public function activate() { + Subsite::changeSubsite($this); + } /** * @@ -897,28 +860,26 @@ JS; } $SQL_permissionCodes = Convert::raw2sql($permissionCodes); - $SQL_permissionCodes = join("','", $SQL_permissionCodes); + $SQL_permissionCodes = join("','", $SQL_permissionCodes); return DataObject::get( - 'SilverStripe\\Security\\Member', + Member::class, "\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')", '', "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\" LEFT JOIN \"Group\" ON \"Group\".\"ID\" = \"Group_Members\".\"GroupID\" LEFT JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\"" - ); + ); + } - } + /** + * Duplicate this subsite + */ + public function duplicate($doWrite = true) { + $duplicate = parent::duplicate($doWrite); - /** - * Duplicate this subsite - */ - public function duplicate($doWrite = true) - { - $duplicate = parent::duplicate($doWrite); - - $oldSubsiteID = Session::get('SubsiteID'); - self::changeSubsite($this->ID); + $oldSubsiteID = Session::get('SubsiteID'); + self::changeSubsite($this->ID); /* * Copy data from this object to the given subsite. Does this using an iterative depth-first search. @@ -931,24 +892,24 @@ JS; list($sourceParentID, $destParentID) = array_pop($stack); $children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", ''); - if ($children) { - foreach ($children as $child) { - self::changeSubsite($duplicate->ID); //Change to destination subsite + if($children) { + foreach($children as $child) { + self::changeSubsite($duplicate->ID); //Change to destination subsite - $childClone = $child->duplicateToSubsite($duplicate, false); - $childClone->ParentID = $destParentID; - $childClone->writeToStage('Stage'); - $childClone->publish('Stage', 'Live'); + $childClone = $child->duplicateToSubsite($duplicate, false); + $childClone->ParentID = $destParentID; + $childClone->writeToStage('Stage'); + $childClone->publish('Stage', 'Live'); - self::changeSubsite($this->ID); //Change Back to this subsite + self::changeSubsite($this->ID); //Change Back to this subsite array_push($stack, [$child->ID, $childClone->ID]); } } } - self::changeSubsite($oldSubsiteID); + self::changeSubsite($oldSubsiteID); - return $duplicate; - } + return $duplicate; + } } diff --git a/code/model/SubsiteDomain.php b/code/model/SubsiteDomain.php index 79505c4..f5e9c81 100644 --- a/code/model/SubsiteDomain.php +++ b/code/model/SubsiteDomain.php @@ -2,22 +2,26 @@ namespace SilverStripe\Subsites\Model; - -use SilverStripe\Core\Convert; +use SilverStripe\Control\Director; +use SilverStripe\Control\Controller; use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\FieldList; -use SilverStripe\Forms\TextField; +use SilverStripe\Forms\OptionsetField; use SilverStripe\ORM\DataObject; /** - * @property text Domain domain name of this subsite. Do not include the URL scheme here - * @property bool IsPrimary Is this the primary subdomain? + * @property string $Domain domain name of this subsite. Can include wildcards. Do not include the URL scheme here + * @property string $Protocol Required protocol (http or https) if only one is supported. 'automatic' implies + * that any links to this subsite should use the current protocol, and that both are supported. + * @property string $SubstitutedDomain Domain name with all wildcards filled in + * @property string $FullProtocol Full protocol including :// + * @property bool $IsPrimary Is this the primary subdomain? */ class SubsiteDomain extends DataObject { - private static $table_name = 'SubsiteDomain'; + private static $table_name = 'SubsiteDomain'; /** * @@ -25,52 +29,100 @@ class SubsiteDomain extends DataObject */ private static $default_sort = "\"IsPrimary\" DESC"; - /** - * - * @var array - */ - private static $db = [ - "Domain" => "Varchar(255)", + /** * + * @var array + */ + private static $db = [ + "Domain" => "Varchar(255)", + "Protocol" => "Enum('http,https,automatic','automatic')", "IsPrimary" => "Boolean", - ]; + ]; + + /*** Specifies that this subsite is http only + */ + const PROTOCOL_HTTP = 'http'; /** - * - * @var array + * Specifies that this subsite is https only */ - private static $has_one = [ - "Subsite" => Subsite::class, - ]; + const PROTOCOL_HTTPS = 'https'; /** - * - * @var array + * Specifies that this subsite supports both http and https */ - private static $summary_fields = [ - 'Domain', - 'IsPrimary', - ]; + const PROTOCOL_AUTOMATIC = 'automatic'; /** - * Whenever a Subsite Domain is written, rewrite the hostmap + * Get the descriptive title for this domain * - * @return void + * @return string */ - public function onAfterWrite() + public function getTitle() { - Subsite::writeHostMap(); + return $this->Domain; } /** - * - * @return \FieldList + * + * @var array + */ + private static $has_one = [ + "Subsite" => Subsite::class, + ]; + + /** + *@config + * @var array + */ + private static $summary_fields=[ + 'Domain', + 'IsPrimary', + ]; + + /*** @config + * @var array */ - public function getCMSFields() - { - $fields = new FieldList( - new TextField('Domain', $this->fieldLabel('Domain'), null, 255), - new CheckboxField('IsPrimary', $this->fieldLabel('IsPrimary')) - ); + private static $casting = array( + 'SubstitutedDomain' => 'Varchar', + 'FullProtocol' => 'Varchar', + 'AbsoluteLink' => 'Varchar', + ); + + /** + * Whenever a Subsite Domain is written, rewrite the hostmap + * + * @return void + */ + public function onAfterWrite() + {Subsite::writeHostMap(); + } + + /** + * + * @return \FieldList + */ + public function getCMSFields() + { + $protocols = array( + self::PROTOCOL_HTTP => _t('SubsiteDomain.PROTOCOL_HTTP', 'http://'), + self::PROTOCOL_HTTPS => _t('SubsiteDomain.PROTOCOL_HTTPS', 'https://'), + self::PROTOCOL_AUTOMATIC => _t('SubsiteDomain.PROTOCOL_AUTOMATIC', 'Automatic') + );$fields = new FieldList( + WildcardDomainField::create('Domain', $this->fieldLabel('Domain'), null, 255) + ->setDescription(_t( + 'SubsiteDomain.DOMAIN_DESCRIPTION', + 'Hostname of this subsite (exclude protocol). Allows wildcards (*).' + )), + OptionsetField::create('Protocol', $this->fieldLabel('Protocol'), $protocols) + ->setDescription(_t( + 'SubsiteDomain.PROTOCOL_DESCRIPTION', + 'When generating links to this subsite, use the selected protocol.
' . + 'Selecting \'Automatic\' means subsite links will default to the current protocol.' + )), CheckboxField::create('IsPrimary', $this->fieldLabel('IsPrimary')) + ->setDescription(_t( + 'SubsiteDomain.PROTOCOL_DESCRIPTION', + 'Mark this as the default domain for this subsite' + ))); $this->extend('updateCMSFields', $fields); return $fields; @@ -85,7 +137,8 @@ class SubsiteDomain extends DataObject { $labels = parent::fieldLabels($includerelations); $labels['Domain'] = _t('SubsiteDomain.DOMAIN', 'Domain'); - $labels['IsPrimary'] = _t('SubsiteDomain.IS_PRIMARY', 'Is Primary Domain'); + $labels['Protocol'] = _t('SubsiteDomain.Protocol', 'Protocol'); + $labels['IsPrimary'] = _t('SubsiteDomain.IS_PRIMARY', 'Is Primary Domain?'); return $labels; } @@ -97,8 +150,85 @@ class SubsiteDomain extends DataObject public function onBeforeWrite() { parent::onBeforeWrite(); - - //strip out any HTML to avoid XSS attacks - $this->Domain = Convert::html2raw($this->Domain); } + + /** + * Get the link to this subsite + * + * @return string + */ + public function Link() + { + return $this->getFullProtocol() . $this->Domain; + } + + /** + * Gets the full protocol (including ://) for this domain + * + * @return string + */ + public function getFullProtocol() + { + switch ($this->Protocol) { + case self::PROTOCOL_HTTPS: + { + return 'https://'; + } + case self::PROTOCOL_HTTP: + { + return 'http://'; + } + default: + { + return Director::protocol(); + } + } + } + + /** + * Retrieves domain name with wildcards substituted with actual values + * + * @todo Refactor domains into separate wildcards / primary domains + * + * @return string + */ + public function getSubstitutedDomain() + { + $currentHost = $_SERVER['HTTP_HOST']; + + // If there are wildcards in the primary domain (not recommended), make some + // educated guesses about what to replace them with: + $domain = preg_replace('/\.\*$/', ".{$currentHost}", $this->Domain ); + + // Default to "subsite." prefix for first wildcard + // TODO Whats the significance of "subsite" inthiscontext?! + $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; + } + + /** + * Get absolute link for this domain + * + * @return string + */ + public function getAbsoluteLink() + { + return $this->getFullProtocol() . $this->getSubstitutedDomain(); + } + + /** + * Get absolute baseURL for this domain + * + * @return string + */ + public function absoluteBaseURL() + { + return Controller::join_links( + $this->getAbsoluteLink(), + Director::baseURL()); + } } diff --git a/code/tasks/SubsiteCopyPagesTask.php b/code/tasks/SubsiteCopyPagesTask.php index 7da2274..0815466 100644 --- a/code/tasks/SubsiteCopyPagesTask.php +++ b/code/tasks/SubsiteCopyPagesTask.php @@ -13,25 +13,27 @@ use SilverStripe\Versioned\Versioned; /** * Handy alternative to copying pages when creating a subsite through the UI. - * Can be used to batch-add new pages after subsite creation, - * or simply to process a large site outside of the UI. + * + * Can be used to batch-add new pages after subsite creation, or simply to + * process a large site outside of the UI. * * Example: sake dev/tasks/SubsiteCopyPagesTask from= to= + * + * @package subsites */ class SubsiteCopyPagesTask extends BuildTask { - protected $title = 'Copy pages to different subsite'; protected $description = ''; - function run($request) + public function run($request) { $subsiteFromId = $request->getVar('from'); if (!is_numeric($subsiteFromId)) { throw new InvalidArgumentException('Missing "from" parameter'); } - $subsiteFrom = DataObject::get_by_id(Subsite::class, $subsiteFromId); + $subsiteFrom = DataObject::get_by_id('Subsite', $subsiteFromId); if (!$subsiteFrom) { throw new InvalidArgumentException('Subsite not found'); } @@ -40,7 +42,7 @@ class SubsiteCopyPagesTask extends BuildTask if (!is_numeric($subsiteToId)) { throw new InvalidArgumentException('Missing "to" parameter'); } - $subsiteTo = DataObject::get_by_id(Subsite::class, $subsiteToId); + $subsiteTo = DataObject::get_by_id('Subsite', $subsiteToId); if (!$subsiteTo) { throw new InvalidArgumentException('Subsite not found'); } @@ -53,12 +55,11 @@ class SubsiteCopyPagesTask extends BuildTask // This will make sure that the new parents on the new subsite are correct, and there are no funny // issues with having to check whether or not the new parents have been added to the site tree // when a page, etc, is duplicated - $stack = [[0, 0]]; + $stack = array(array(0,0)); while (count($stack) > 0) { list($sourceParentID, $destParentID) = array_pop($stack); - $children = Versioned::get_by_stage('SilverStripe\\CMS\\Model\\SiteTree', 'Live', - "\"ParentID\" = $sourceParentID", ''); + $children = Versioned::get_by_stage(SiteTree::class, 'Live', "\"ParentID\" = $sourceParentID", ''); if ($children) { foreach ($children as $child) { @@ -74,7 +75,7 @@ class SubsiteCopyPagesTask extends BuildTask $childClone->ParentID = $destParentID; $childClone->writeToStage('Stage'); $childClone->publish('Stage', 'Live'); - array_push($stack, [$child->ID, $childClone->ID]); + array_push($stack, array($child->ID, $childClone->ID)); $this->log(sprintf('Copied "%s" (#%d, %s)', $child->Title, $child->ID, $child->Link())); } @@ -84,7 +85,7 @@ class SubsiteCopyPagesTask extends BuildTask } } - function log($msg) + public function log($msg) { echo $msg . "\n"; } diff --git a/composer.json b/composer.json index 7579e94..3fa9e22 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "silverstripe/subsites", "description": "Run multiple sites from a single SilverStripe install.", + "license": "BSD-3-Clause", "type": "silverstripe-module", "keywords": ["silverstripe", "subsites", "multisite"], "authors": [ diff --git a/css/LeftAndMain_Subsites.css b/css/LeftAndMain_Subsites.css index c06a04d..436402c 100644 --- a/css/LeftAndMain_Subsites.css +++ b/css/LeftAndMain_Subsites.css @@ -95,6 +95,7 @@ body.SubsiteAdmin .right form #URL .fieldgroup * { display:none; } -#Root_DetailsView .subsites-move-dropdown{ +#Root_DetailsView .subsites-move-dropdown, +#Form_ItemEditForm .subsites-move-dropdown { display:block; } diff --git a/docs/en/introduction.md b/docs/en/introduction.md index 07dbe98..24eabff 100644 --- a/docs/en/introduction.md +++ b/docs/en/introduction.md @@ -1,84 +1,9 @@ -## Introduction +# Subsites -Subsites is a module to allow you manage multiple related sites from a single CMS interface. - -When Subsites is installed your existing site is defined as the main site, you will be then be able to create related subsites under the main site. - -One of the benefits of subsites is that it is easy to copy pages between the subsites and you have access to all of -the assets across all of the subsites. - -Subsites is not for running unrelated websites on a single SilverStripe instance so if 2 sites have different vhosts -you will not be able to run them with Subsites on a single SilverStripe instance. - -With Subsites you can set up users to have access to all subsites or just a selection of subsites. - -## When to use the Subsites module - -* You have a clear parent/main website and a set of subsites associated with it. -* You have a group of simple, commonly themed websites -* Your websites contain some overlapping content, and you want to avoid the redundancy of making the same update to content that exists on multiple CMS installations. -* You have simple content websites that use the same templates and modules, and are managed by the same team, but have different themes. -* Content editing of the subsites is through the same team, so there is no problem around editors having access to all the files for each subsite, nor around having CMS admin(s) who control access to all the sites. - -## When to avoid the Subsites module - -* You have constraints on what content CMS editors can and cannot view between subsites. -* You have a business critical website, and adding complexity around changes and releases for subsites creates unwanted risk. -* There is a significant security risk around putting the websites into the same CMS installation. This could be the risk of content bleeding between sites, or the risk of an editor on one site gaining access to content on the other site. -* Websites that are distinct and use customised modules and/or bespoke code -* Websites that are owned by different business units and/or managed by different development and web teams. (not technical, it just organisational complexity). - -It is important to remember that the only unique trait each site can have is its theme (look and feel), whilst all the other building blocks of the site — the code, database, and modules — are shared. Therefore, one of the biggest drawbacks to using subsites is exposure to a single point of failure. If there is a bug in code that is only used on one subsite, it nevertheless affects all other subsites, because they share a codebase. Similarly, if the database becomes corrupt, all subsites are affected. Further, it is not possible to create or restore backups of any given subsite. Backups must represent and replace the entire collection of sites. - -## Access - -Access to certain subsites can be limited to administrators based on the groups they are in. -So for example if you had a couple of subsites you could create a group for each subsite and then specify that the -group had access to all subsites or just a specific subsites. -To access this functionality go to - - -Security -> Groups - -![alt text](_images/subsite-admin-security-group.png "Groups") - -Select the group you want to modify and then go to the Subsites tab - -You can also limit the page types that are available for a subsite (but not the main site). -This can be done via accessing the particular subsite you want to amend via the Subsite admin section, underneath the -Subsite theme will be a link called 'Disallow page types?' clicking on that link will display a list of checkboxes for -all of the page types which can be selected to disable that page type for the subsite you are editing. -This is useful when you create a content editor and you do not want them to be able to add certain page types. - -## Theme -A theme is group of templates, images and CSS for the look of a website. -When you are using Subsites you may have different themes installed for your site so you could apply different -themes for each subsite. - -## Page types -Page types refer to the type of pages that can be set up on a site. -A page type will have certain features and functionality some examples on SilverStripe would be 'Page', 'HomePage' -and 'ErrorPage' these all differ to each other in what they would be used for so you would use Page for any pages -underneath the HomePage. - - -You would only have one HomePage for your site and you may have some logic to only allow you to create one of these -pages, ErrorPage would only be used for error pages and would be designed to be very minimal to work in situations -where the site is experiencing difficulties like no DB access. - - -You can set up a Subsite to only work with certain page types so you may have a page type with a contact form for a -particular department so you may set up a new subsite and not allow that page type to be used on a particular subsite. -You will not be able to filter Page Types for the main site. - -## Assets -Assets are files that have been uploaded via the CMS. -It is suggested to use a naming convention for files designated to be used on a particular subsite or to create folders -for each subsite to help organise them. +[User guide](userguide/index.md) ## FAQ - ### How can I restrict a content author to a particular subsite? diff --git a/docs/en/user_manual.pdf b/docs/en/user_manual.pdf deleted file mode 100644 index 2a7e258..0000000 Binary files a/docs/en/user_manual.pdf and /dev/null differ diff --git a/docs/en/_images/copy-page-to-subsite.jpg b/docs/en/userguide/_images/copy-page-to-subsite.jpg similarity index 100% rename from docs/en/_images/copy-page-to-subsite.jpg rename to docs/en/userguide/_images/copy-page-to-subsite.jpg diff --git a/docs/en/_images/copy-structure.jpg b/docs/en/userguide/_images/copy-structure.jpg similarity index 100% rename from docs/en/_images/copy-structure.jpg rename to docs/en/userguide/_images/copy-structure.jpg diff --git a/docs/en/_images/disallow-page-types-result.png b/docs/en/userguide/_images/disallow-page-types-result.png similarity index 100% rename from docs/en/_images/disallow-page-types-result.png rename to docs/en/userguide/_images/disallow-page-types-result.png diff --git a/docs/en/_images/disallow-page-types.png b/docs/en/userguide/_images/disallow-page-types.png similarity index 100% rename from docs/en/_images/disallow-page-types.png rename to docs/en/userguide/_images/disallow-page-types.png diff --git a/docs/en/_images/group-subsites-access.png b/docs/en/userguide/_images/group-subsites-access.png similarity index 100% rename from docs/en/_images/group-subsites-access.png rename to docs/en/userguide/_images/group-subsites-access.png diff --git a/docs/en/_images/subsite-admin-security-group.png b/docs/en/userguide/_images/subsite-admin-security-group.png similarity index 100% rename from docs/en/_images/subsite-admin-security-group.png rename to docs/en/userguide/_images/subsite-admin-security-group.png diff --git a/docs/en/_images/subsite-configuration.jpg b/docs/en/userguide/_images/subsite-configuration.jpg similarity index 100% rename from docs/en/_images/subsite-configuration.jpg rename to docs/en/userguide/_images/subsite-configuration.jpg diff --git a/docs/en/_images/subsites-dropdown.png b/docs/en/userguide/_images/subsites-dropdown.png similarity index 100% rename from docs/en/_images/subsites-dropdown.png rename to docs/en/userguide/_images/subsites-dropdown.png diff --git a/docs/en/_images/subsites-virtual-page.jpg b/docs/en/userguide/_images/subsites-virtual-page.jpg similarity index 100% rename from docs/en/_images/subsites-virtual-page.jpg rename to docs/en/userguide/_images/subsites-virtual-page.jpg diff --git a/docs/en/_images/view-subsites.jpg b/docs/en/userguide/_images/view-subsites.jpg similarity index 100% rename from docs/en/_images/view-subsites.jpg rename to docs/en/userguide/_images/view-subsites.jpg diff --git a/docs/en/userguide/index.md b/docs/en/userguide/index.md new file mode 100644 index 0000000..7627044 --- /dev/null +++ b/docs/en/userguide/index.md @@ -0,0 +1,48 @@ +title: Working with multiple websites +summary: Setting up and editing multiple websites using SilverStripe + +# Working with multiple sites + +## In this section: + +* Understand subsites +* Learn how to create and delete subsites +* Learn how to manage subsite permissions +* Enable/Disable public access to subsites +* Learn how to create and use subsite templates +* Learn how to edit existing subsites +* Sharing content between the main site and subsites + +## Before we begin: + +* Make sure you have the SilverStripe [Subsites](http://addons.silverstripe.org/add-ons/silverstripe/subsites) module installed. +* Make sure you are in the "Subsites" section on the Navigation Tabs. +* Make sure you have full administrative rights on your site. + +## Understanding subsites + +Subsites is a module to allow you manage multiple related sites from a single CMS interface. Because all sites run on a single installation of SilverStripe, they can share users, content and assets. They can all use the same templates, or each use different ones. + +When Subsites is installed your existing site is defined as the main site, you will be then be able to create related subsites under the main site. + +So for example you may have an international presence and you want to create a subsite for a country where you do business which is geared just for that market. You could create a subsite for this and have all information related to that country kept under this subsite, you can also set up a subdomain for this site. + +One of the benefits of subsites is that it is easy to copy pages between the subsites and you have access to all of the assets across all of the subsites. + +Subsites is not for running unrelated websites on a single SilverStripe instance so if two sites have different vhosts you will not be able to run them with Subsites on a single SilverStripe instance. + +With Subsites you can set up users to have access to all subsites or just a selection of subsites. + +## Common subsite uses + +Subsites can be used for various different reasons here are some of the common ones: + +* Setting up a subsite for a small campaign so for example a clothing company may set up a summer or winter subsite to market just that season of clothing. +* Locking down a particular subsite you may create a particular department like recruitment who would have access to create and edit pages for their particular subsite but they would not be able to modify the main website. +* Running sub-domains on a single SilverStripe instance, with subsites if a sub-domain is pointing to the same instance and has been setup correctly you can manage this via a single CMS instance. +* Subsites can not be used to run multiple websites on a single instance. Subsites does not allow you to run multiple domains/vhosts on a single instance. + +## Documentation + +* [Set up](set_up.md) +* [Working with subsites](working_with.md) \ No newline at end of file diff --git a/docs/en/set_up.md b/docs/en/userguide/set_up.md similarity index 62% rename from docs/en/set_up.md rename to docs/en/userguide/set_up.md index 0fc8355..714d256 100644 --- a/docs/en/set_up.md +++ b/docs/en/userguide/set_up.md @@ -2,44 +2,42 @@ ## Creating subsites -To view, edit and create subsites, go to the *Subsite* section of the CMS. Click *Search* to view a list of all -existing subsites. +To view, edit and create subsites, go to the *Subsite* section of the CMS. Click *Search* to view a list of all existing subsites. ![View subsites](_images/view-subsites.jpg) -Click on a subsite name to edit its details. Click the red X to delete a subsite (you will be asked for confirmation.) -To create a new subsite, click *Add Subsite*. This opens the *Subsite configuration* subsection. +Click on a subsite name to edit its details. To create a new subsite, click *Add Subsite*. This opens the *Subsite configuration* subsection. + +## Deleting a subsite + +Click the red X to delete a subsite (you will be asked for confirmation.) + +## Subsite configuration ![Subsite configuration](_images/subsite-configuration.jpg) You can configure the following details for a subsite: * *Name of subsite:* This is the display name for the site in the CMS (not public-facing) - * *Domains for this subsite:* Lets you add one or more domains for this subsite, e.g., subsite.co.nz, subsite.org.nz, -subsite.com + * *Domains for this subsite:* Lets you add one or more domains for this subsite, e.g., subsite.co.nz, subsite.org.nz, subsite.com * *Language:* Sets the language for the subsite. This affects the spellchecker (not the CMS interface language) * *Default site:* If your site is accessed by a domain not listed in any subsites, this subsite is shown as default. * *Enable public access:* Enables/disables the subsite. Corresponds to the *Active Subsite* column. - * *Theme:* Shows the list of available themes that exist in the themes directory. The subsite will use the templates -and styles from the selected theme. - * *Disallow page types:* Lets you mark some page types to prevent being used within this subsite. - * *Copy structure from:* Gives you the option to select an existing subsite from which to copy pages and files (see - "Copying subsites" for more information about this) + * *Theme:* Shows the list of available themes that exist in the themes directory. The subsite will use the templates and styles from the selected theme. + * *Disallow page types:* Lets you mark some page types to prevent them being used within this subsite (but not the main site). Clicking on that link will display a list of checkboxes for all of the page types which can be selected to disable that page type for the subsite you are editing. This is useful when you create a content editor and you do not want them to be able to add certain page types. + * *Copy structure from:* Gives you the option to select an existing subsite from which to copy pages and files (see "Copying subsites" for more information about this) ## Roles When creating roles, you can assign the following subsite-specific permissions: - * *Access to 'Subsites' section:* Shows the *Subsite* section in the CMS, allowing you to manage subsites for your -site (ie, create, edit, view.) + * *Access to 'Subsites' section:* Shows the *Subsite* section in the CMS, allowing you to manage subsites for your site (ie, create, edit, view.) * *Manage subsites for groups:* Ability to limit the permissions for a group to one or more subsites. - * *Manage assets for subsites:* Ability to select the subsite to which an asset folder belongs (also requires access to -*Files* section) + * *Manage assets for subsites:* Ability to select the subsite to which an asset folder belongs (also requires access to *Files* section) ## Groups -Groups can have access to all sites, or to one or more specific subsites. If you have different staff in charge of each -subsite, you probably want to create a separate group for each subsite. +Groups can have access to all sites, or to one or more specific subsites. If you have different staff in charge of each subsite, you probably want to [create](#creating-groups) a separate group for each subsite. The dropdown in the upper left corner of the CMS indicates which subsite you are currently on. @@ -50,7 +48,19 @@ these subsites* to reveal a checklist of all available sites. ![Group subsites access](_images/group-subsites-access.png) +### Creating groups + +Access to certain subsites can be limited to administrators based on the groups they are in. +So for example if you had a couple of subsites you could create a group for each subsite and then specify that the group had access to all subsites or just a specific subsites. + +To access this functionality go to Security -> Groups + +![Creating Groups](_images/subsite-admin-security-group.png "Groups") + +Select the group you want to modify and then go to the Subsites tab + ## Copying subsites + Duplicating subsites can be useful if you want to create several subsites based on the same general site structure. You can set up a collection of pages, files, and images and use it as a template. When you create a new subsite, instead of starting from scratch you can copy it all from your existing subsite. This will copy all pages, files and images from that subsite into your new subsite. To create a new subsite template, create a new subsite described above under "Creating subsites" but don't add any domains. Add a name that will make it easy to see that it is a template. Select the new template from the subsites dropdown in the upper right and create the pages and add the files and images you'd like to become part of the @@ -60,7 +70,14 @@ When you create a new subsite, you can now choose to Copy structure from your te ![Copy subsite structure](_images/copy-structure.jpg) -## Disable particular page types from a subsite +## Page types +Page types refer to the type of pages that can be set up on a site. A page type will have certain features and functionality some examples on SilverStripe would be 'Page', 'HomePage' and 'ErrorPage' these all differ to each other in what they would be used for so you would use Page for any pages +underneath the HomePage. + +You would only have one HomePage for your site and you may have some logic to only allow you to create one of these pages, ErrorPage would only be used for error pages and would be designed to be very minimal to work in situations where the site is experiencing difficulties (like no database access). + +### Disable particular page types from a subsite + Sometimes, you will have two or more websites that are very similar, but have some small differences. For example, a head office and its 3 satellite offices may have 4 subsites, but only the head office site will have a "Company News" section on the site. In this instance, you can still use the subsites module, and use the 'Disallow page types' ability to remove certain page types from being created on subsites. 1. Create a new subsite as you normally would via the Subsites section in the CMS @@ -69,7 +86,7 @@ Sometimes, you will have two or more websites that are very similar, but have so 4. Select the page types that you wish to forbid from this subsite. 5. Click the Save button at the bottom of the section. -Note that this process is exactly the same when editing an existing subsite, you just select the subsite you want to remove page types from instead of creating a new subsite. +*Note:* This process is exactly the same when editing an existing subsite, you just select the subsite you want to remove page types from instead of creating a new subsite. Also you cannot not filter Page Types for the main site. ![Disallow page types screenshot](_images/disallow-page-types.png) @@ -79,4 +96,10 @@ Now, whenever someone wants to create a new page on the subsite (the 'London Bra The page type blacklist applies only to future pages, not existing ones. If an administrator blacklists a defined page type on a subsite, existing pages of that type will remain on the subsite. The administrator (or another user) can later change it to a type that is not blacklisted after the page is published; however, once a user does that, that "grandfathered" pagetype will no longer be available to the published page. -For example, say a subsite user publishes a new Company Page before it was forbidden by an administrator. Later, a user with full administrative rights decides to disallow Company Pages from being created on the subsite. This restriction will only apply to all future pages that are published, but not to existing Company Pages. The full administrator (or any user with sufficient publishing rights) will have to manually convert any published Company Pages on the subsite to that of another allowed type. \ No newline at end of file +For example, say a subsite user publishes a new Company Page before it was forbidden by an administrator. Later, a user with full administrative rights decides to disallow Company Pages from being created on the subsite. This restriction will only apply to all future pages that are published, but not to existing Company Pages. The full administrator (or any user with sufficient publishing rights) will have to manually convert any published Company Pages on the subsite to that of another allowed type. + +## Themes +A theme is group of templates, images and CSS for the look of a website. When you are using Subsites you may have different themes installed for your site so you could apply different themes for each subsite. + +## Assets +Assets are files that have been uploaded via the CMS. It is suggested to use a naming convention for files designated to be used on a particular subsite or to create folders for each subsite to help organise them. \ No newline at end of file diff --git a/docs/en/working_with.md b/docs/en/userguide/working_with.md similarity index 85% rename from docs/en/working_with.md rename to docs/en/userguide/working_with.md index 19e32ac..918489f 100644 --- a/docs/en/working_with.md +++ b/docs/en/userguide/working_with.md @@ -2,11 +2,11 @@ ## Managing content across subsites -Select a subsite from the dropdown in the upper left to display the content for that site in the site tree. +To edit a particular subsite, choose the subsite from the dropdown menu in the left-hand menu. ![Subsites dropdown](_images/subsites-dropdown.png) -## Subsites virtual pages +### Subsites virtual pages You can pull in the content from a page that resides on another subsite by creating a page of the type **Subsites Virtual Page**. Pick the subsite from which you want to pull the content, then select the page. As with regular virtual @@ -15,7 +15,7 @@ the original content changes. ![Subsites virtual page](_images/subsites-virtual-page.jpg) -## Duplicating pages from the main site +### Duplicating pages from the main site If you have an existing page on the main site that you would like to copy to a subsite, all you need to do is: diff --git a/lang/ar.yml b/lang/ar.yml index 54e70f4..dc91b8a 100644 --- a/lang/ar.yml +++ b/lang/ar.yml @@ -1,13 +1,9 @@ ar: - SubsiteAdmin: - MENUTITLE: مواقع فرعية - SubsiteXHRController: - MENUTITLE: المراقب XHR للموقع الفرعي ASSETADMIN: SUBSITENOTICE: 'يمكن الوصول إلى المجلدات والملفات التي تم إنشاؤها في الموقع الرئيسي من طرف كل المواقع الفرعية.' FileSubsites: AllSitesDropdownOpt: 'كافة المواقع' - SubsiteFieldLabel: الموقع الفرعي + SubsiteFieldLabel: 'الموقع الفرعي' GridFieldAddFromTemplateButton: AddFromTemplate: 'اضف جديد من القالب' GroupSubsites: @@ -17,7 +13,7 @@ ar: GlobalGroup: 'المجموعة العامة' MANAGE_SUBSITES: 'إدارة مواقع فرعية للمجموعات' MANAGE_SUBSITES_HELP: 'القدرة على الحد من أذونات مجموعة ما على موقع فرعي واحدة أو أكثر.' - SECURITYTABTITLE: مواقع فرعية + SECURITYTABTITLE: 'مواقع فرعية' LeftAndMainSubsites: Saved: 'تمّ الحفظ، يرجى تحديث الصفحات ذات الصلة.' SiteTreeSubsites: @@ -28,27 +24,30 @@ ar: CopyMessage: 'إنشاء نسخة من {title}' CustomExtraMeta: 'العلامات الوصفية المخصصة' CustomMetaDescription: الوصف - CustomMetaKeywords: كلمات البحث + CustomMetaKeywords: 'كلمات البحث' CustomMetaTitle: عنوان DOMAINSAVEFIRST: 'لا يمكنك إضافة النطاقات إلا بعد القيام بالحفظ لأول مرة' DomainsHeadline: 'نطاقات هذا الموقع الفرعي' DomainsListTitle: النطاقات IsPublicHeaderField: 'موقع فرعي نشط' NOTEMPLATE: 'بدون قالب' - PLURALNAME: المواقع الفرعية + PLURALNAME: 'المواقع الفرعية' PageTypeBlacklistField: 'عدم السماح بأصناف الصفحات؟' - SINGULARNAME: الموقع الفرعي + SINGULARNAME: 'الموقع الفرعي' SiteConfigSubtitle: 'هنا سطر الوصف الخاص بك' SiteConfigTitle: 'اسم موقعك' - TabTitleConfig: المواصفات + TabTitleConfig: 'المواصفات' ValidateTitle: 'الرجاء إضافة "عنوان"' + SubsiteAdmin: + MENUTITLE: 'مواقع فرعية' SubsiteDomain: DOMAIN: نطاق - IS_PRIMARY: 'هو نطاق أساسي' PLURALNAME: 'نطاقات موقع فرعي' SINGULARNAME: 'نطاق موقع فرعي' SubsiteReportWrapper: - ReportDropdown: المواقع + ReportDropdown: 'المواقع' + SubsiteXHRController: + MENUTITLE: 'المراقب XHR للموقع الفرعي' Subsites: DefaultSiteFieldLabel: 'الموقع الافتراضي' DomainFieldLabel: النطاق @@ -58,10 +57,9 @@ ar: PageTypeBlacklistFieldLabel: 'نوع الصفحة قائمة سوداء' PrimaryDomainFieldLabel: 'النطاق الأساسي' RedirectURLFieldLabel: 'إعادة توجيه عنوان موقع الويب' - ThemeFieldLabel: المحور + ThemeFieldLabel: 'المحور' TitleFieldLabel: 'اسم الموقع الفرعي' SubsitesVirtualPage: DESCRIPTION: 'يعرض محتوى صفحة على موقع فرعي آخر' - PLURALNAME: 'قاعدة الصفحات' SINGULARNAME: 'الصفحة الإفتراضية للمواقع الفرعية' - SubsiteField: الموقع الفرعي + SubsiteField: 'الموقع الفرعي' diff --git a/lang/cs.yml b/lang/cs.yml new file mode 100644 index 0000000..2bdc42b --- /dev/null +++ b/lang/cs.yml @@ -0,0 +1,34 @@ +cs: + LeftAndMain_Menu: + Hello: Ahoj + LOGOUT: 'Odhlásit se' + SiteTreeSubsites: + CopyAction: Kopírovat + Subsite: + CustomExtraMeta: 'Vlastní meta tagy' + CustomMetaDescription: Popis + CustomMetaKeywords: 'Klíčová slova' + CustomMetaTitle: Název + DOMAINSAVEFIRST: 'Domény můžete přidávat až po uložení' + DomainsHeadline: 'Domény pro tento web' + DomainsListTitle: Domény + SiteConfigSubtitle: 'Slogan Vašeho webu' + SiteConfigTitle: 'Název Vašeho webu' + TabTitleConfig: Konfigurace + ValidateTitle: 'Prosím vložte "Název"' + SubsiteAdmin: + MENUTITLE: Subsites + SubsiteDomain: + DOMAIN: Doména + PLURALNAME: 'Domény webů' + SINGULARNAME: 'Doména webu' + SubsiteReportWrapper: + ReportDropdown: Weby + Subsites: + DefaultSiteFieldLabel: 'Výchozí web' + DomainFieldLabel: Doména + LanguageFieldLabel: Jazyk + ThemeFieldLabel: Téma + TitleFieldLabel: 'Název subsite' + SubsitesVirtualPage: + PLURALNAME: 'Základní stránky' diff --git a/lang/de.yml b/lang/de.yml index 02e8379..3977ef1 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -1,10 +1,8 @@ de: - SubsiteAdmin: - MENUTITLE: Subsites - SubsiteXHRController: - MENUTITLE: SubsiteXHRController ASSETADMIN: SUBSITENOTICE: 'Auf Ordner und Dateien der Hauptseite kann von allen Subsites zugegriffen werden.' + DomainNameField: + INVALID_DOMAIN: 'Ungültige Domain' FileSubsites: AllSitesDropdownOpt: 'Alle Subseiten' SubsiteFieldLabel: Subseite @@ -19,7 +17,10 @@ de: MANAGE_SUBSITES_HELP: 'Möglichkeit, die Berechtigungen einer Gruppe auf bestimmte Subsites zu beschränken.' SECURITYTABTITLE: Subsites LeftAndMainSubsites: - Saved: 'Gespeichert.' + Saved: Gespeichert. + LeftAndMain_Menu: + Hello: Hallo + LOGOUT: Abmelden SiteTreeSubsites: CopyAction: Kopieren CopyToSubsite: 'Seite auf Subseite Kopieren' @@ -42,26 +43,37 @@ de: SiteConfigTitle: 'Name Ihrer Website' TabTitleConfig: Einstellungen ValidateTitle: 'Bitte geben Sie einen Titel an' + SubsiteAdmin: + MENUTITLE: Subsites SubsiteDomain: DOMAIN: Domain - IS_PRIMARY: 'Ist primäre Domain' + DOMAIN_DESCRIPTION: 'Hostname dieser Subsite (ohne Protokol). Joker (*) ist erlaubt.' + IS_PRIMARY: 'Ist Hauptdomain?' PLURALNAME: 'Subsite Domains' + PROTOCOL_AUTOMATIC: Automatisch + PROTOCOL_HTTP: 'http://' + PROTOCOL_HTTPS: 'https://' + Protocol: Protokoll SINGULARNAME: 'Subsite Domain' SubsiteReportWrapper: ReportDropdown: Seiten + SubsiteXHRController: + MENUTITLE: SubsiteXHRController Subsites: DefaultSiteFieldLabel: 'Standard Seite' DomainFieldLabel: Domäne IsPublicFieldLabel: 'Öffentlich zugänglich' LanguageFieldLabel: Sprache - MainSiteTitle: 'Hauptsite' + MainSiteTitle: Hauptsite PageTypeBlacklistFieldLabel: 'Ausgeschlossene Seitentypen' PrimaryDomainFieldLabel: 'Primäre Domain' - RedirectURLFieldLabel: 'Weierleitungs-URL' + RedirectURLFieldLabel: Weierleitungs-URL ThemeFieldLabel: Theme TitleFieldLabel: 'Name der Subsite' SubsitesVirtualPage: DESCRIPTION: 'Zeigt den Inhalt einer anderen Seite von einer anderen Subsite an' - PLURALNAME: 'Subsites Virtuelle Seiten' + PLURALNAME: 'Basis Seiten' SINGULARNAME: 'Subsites Virtuelle Seite' SubsiteField: Subsite + VirtualPage: + EDITCONTENT: 'Klicken Sie hier, um den Inhalt zu bearbeiten' diff --git a/lang/en.yml b/lang/en.yml index 4a4bda6..c57c4fe 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -1,6 +1,8 @@ en: ASSETADMIN: SUBSITENOTICE: 'Folders and files created in the main site are accessible by all subsites.' + DomainNameField: + INVALID_DOMAIN: 'Invalid domain name' FileSubsites: AllSitesDropdownOpt: 'All sites' SubsiteFieldLabel: Subsite @@ -22,6 +24,8 @@ en: SiteTreeSubsites: CopyAction: Copy CopyToSubsite: 'Copy page to subsite' + CopyToSubsiteWithChildren: 'Include children pages?' + SubsiteOperations: 'Subsite Operations' Subsite: COPYSTRUCTURE: 'Copy structure from:' CopyMessage: 'Created a copy of {title}' @@ -45,8 +49,14 @@ en: MENUTITLE: Subsites SubsiteDomain: DOMAIN: Domain - IS_PRIMARY: 'Is Primary Domain' + DOMAIN_DESCRIPTION: 'Hostname of this subsite (exclude protocol). Allows wildcards (*).' + IS_PRIMARY: 'Is Primary Domain?' PLURALNAME: 'Subsite Domains' + PROTOCOL_AUTOMATIC: Automatic + PROTOCOL_DESCRIPTION: 'Mark this as the default domain for this subsite' + PROTOCOL_HTTP: 'http://' + PROTOCOL_HTTPS: 'https://' + Protocol: Protocol SINGULARNAME: 'Subsite Domain' SubsiteReportWrapper: ReportDropdown: Sites diff --git a/lang/eo.yml b/lang/eo.yml index 19bbe25..b02b296 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -1,10 +1,8 @@ eo: - SubsiteAdmin: - MENUTITLE: Subretejoj - SubsiteXHRController: - MENUTITLE: SubsiteXHRController ASSETADMIN: SUBSITENOTICE: 'Dosierujoj kaj dosieroj kreitaj en la ĉefa retejo estas alireblaj de ĉiuj retejoj' + DomainNameField: + INVALID_DOMAIN: 'Nevalida domajna nomo' FileSubsites: AllSitesDropdownOpt: 'Ĉiuj retejoj' SubsiteFieldLabel: Subretejo @@ -20,9 +18,14 @@ eo: SECURITYTABTITLE: Subsites LeftAndMainSubsites: Saved: 'Konservita, bonvole ĝisdatigi rilatajn paĝojn.' + LeftAndMain_Menu: + Hello: Saluton + LOGOUT: Elsaluti SiteTreeSubsites: CopyAction: Kopio CopyToSubsite: 'Kopii paĝon al subretejo' + CopyToSubsiteWithChildren: 'Ĉu inkluzivi paĝidojn?' + SubsiteOperations: 'Subretejaj operacioj' Subsite: COPYSTRUCTURE: 'Kopii strukturon de:' CopyMessage: 'Kreis kopion de {title}' @@ -42,13 +45,23 @@ eo: SiteConfigTitle: 'Nomo de via retejo' TabTitleConfig: Agordaro ValidateTitle: 'Bonvole aldonu "Titolon"' + SubsiteAdmin: + MENUTITLE: Subretejoj SubsiteDomain: DOMAIN: Domajno - IS_PRIMARY: 'Estas unuaranga domajno' + DOMAIN_DESCRIPTION: 'Gastiga nomo de ĉi tiu subretejo (ellasu protokolon). Permesas ĵokerojn (*).' + IS_PRIMARY: 'Ĉu unuaranga domajno?' PLURALNAME: 'Subretejaj domajnoj' + PROTOCOL_AUTOMATIC: Aŭtomata + PROTOCOL_DESCRIPTION: 'Marki ĉi tion kiel la aprioran domajnon por ĉi tiu subretejo' + PROTOCOL_HTTP: 'http://' + PROTOCOL_HTTPS: 'https://' + Protocol: Protokolo SINGULARNAME: 'Subreteja domajno' SubsiteReportWrapper: ReportDropdown: Retejoj + SubsiteXHRController: + MENUTITLE: SubsiteXHRController Subsites: DefaultSiteFieldLabel: 'Apriora retejo' DomainFieldLabel: Domajno @@ -62,6 +75,8 @@ eo: TitleFieldLabel: 'Nomo de subretejo' SubsitesVirtualPage: DESCRIPTION: 'Vidigas la enhavon de paĝo en alia subretejo' - PLURALNAME: 'Virtualaj paĝoj de subretejoj' + PLURALNAME: 'Bazaj paĝoj' SINGULARNAME: 'Virtuala paĝo de subretejoj' SubsiteField: Subretejo + VirtualPage: + EDITCONTENT: 'Alklaku ĉi tie por redakti la enhavon' diff --git a/lang/fa_IR.yml b/lang/fa_IR.yml new file mode 100644 index 0000000..265d84d --- /dev/null +++ b/lang/fa_IR.yml @@ -0,0 +1,46 @@ +fa_IR: + DomainNameField: + INVALID_DOMAIN: 'نام دامنه نامعتبر' + FileSubsites: + AllSitesDropdownOpt: 'تمامی سایت ها' + SubsiteFieldLabel: 'زیر سایت ها' + GroupSubsites: + ACCESSALL: 'تمامی زیر سایت ها' + ACCESSONLY: 'فقط این زیر سایت ها' + SECURITYTABTITLE: 'زیر سایت ها' + LeftAndMain_Menu: + Hello: 'سلام' + LOGOUT: خروج + Subsite: + CustomMetaKeywords: 'کلید واژه ها' + CustomMetaTitle: عنوان + DomainsHeadline: 'دامنه های این زیر سایت' + DomainsListTitle: 'دامنه ها' + IsPublicHeaderField: 'زیر سایت فعال' + NOTEMPLATE: 'بدون قالب' + PLURALNAME: 'زیر سایت ها' + SINGULARNAME: 'زیر سایت' + SiteConfigTitle: 'نام سایت شما' + TabTitleConfig: پیکربندی + SubsiteAdmin: + MENUTITLE: 'زیر سایت ها' + SubsiteDomain: + DOMAIN: 'دامنه' + PLURALNAME: 'دامنه های زیر سایت' + PROTOCOL_AUTOMATIC: 'به صورت خودکار' + Protocol: پروتکل + SINGULARNAME: 'دمنه زیرسایت' + SubsiteReportWrapper: + ReportDropdown: 'سایت ها' + Subsites: + DefaultSiteFieldLabel: 'سایت پیش فرض' + DomainFieldLabel: 'دامنه' + LanguageFieldLabel: زبان + PrimaryDomainFieldLabel: 'دامنه اولیه' + ThemeFieldLabel: پوسته + TitleFieldLabel: 'نام زیر سایت' + SubsitesVirtualPage: + PLURALNAME: 'صفحه اصلی' + SubsiteField: 'زیر سایت' + VirtualPage: + EDITCONTENT: 'برای ویرایش محتوا اینجا را کلیک کنید' diff --git a/lang/fi.yml b/lang/fi.yml index ba4d7c5..e5342ad 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -1,8 +1,4 @@ fi: - SubsiteAdmin: - MENUTITLE: Alasivustot - SubsiteXHRController: - MENUTITLE: SubsiteXHRController ASSETADMIN: SUBSITENOTICE: 'Kansiot ja tiedostot, jotka on luotu pääsivustolla, ovat käytettävissä kaikissa alisivustoissa.' FileSubsites: @@ -14,7 +10,7 @@ fi: ACCESSALL: 'Kaikki alasivustot' ACCESSONLY: 'Vain nämä alasivustot' ACCESSRADIOTITLE: 'Anna tälle ryhmälle pääsy kohteeseen' - GlobalGroup: 'Globaaliryhmä' + GlobalGroup: Globaaliryhmä MANAGE_SUBSITES: 'Hallinnoi ryhmien alisivustoja' MANAGE_SUBSITES_HELP: 'Mahdollisuus rajoittaa ryhmän oikeuksia yhdelle tai useammalle alisivustolle.' SECURITYTABTITLE: Alisivustot @@ -38,30 +34,33 @@ fi: PLURALNAME: Alasivustot PageTypeBlacklistField: 'Kiellä sivutyyppien käyttö?' SINGULARNAME: Alisivusto - SiteConfigSubtitle: 'Iskulauseesi' + SiteConfigSubtitle: Iskulauseesi SiteConfigTitle: 'Sivuston nimi' TabTitleConfig: Asetukset ValidateTitle: 'Lisää "Otsikko"' + SubsiteAdmin: + MENUTITLE: Alasivustot SubsiteDomain: DOMAIN: Domain - IS_PRIMARY: 'On päädomain' PLURALNAME: 'Alisivuston domain-osoitteet' SINGULARNAME: 'Alisivuston domain-osoite' SubsiteReportWrapper: ReportDropdown: Sivustot + SubsiteXHRController: + MENUTITLE: SubsiteXHRController Subsites: - DefaultSiteFieldLabel: 'Oletussivusto' + DefaultSiteFieldLabel: Oletussivusto DomainFieldLabel: Domain IsPublicFieldLabel: 'Aktivoi julkinen pääsy' LanguageFieldLabel: Kieli - MainSiteTitle: 'Pääsivusto' + MainSiteTitle: Pääsivusto PageTypeBlacklistFieldLabel: 'Sivutyyppien mustalista' - PrimaryDomainFieldLabel: 'Oletusdomain' + PrimaryDomainFieldLabel: Oletusdomain RedirectURLFieldLabel: 'Edelleenohjaus URL' ThemeFieldLabel: Teema TitleFieldLabel: 'Alisivuston nimi' SubsitesVirtualPage: DESCRIPTION: 'Näyttää sisällön toisen alisivuston sivulta' - PLURALNAME: 'Alisivustojen virtuaaliset sivut' + PLURALNAME: Pohjasivut SINGULARNAME: 'Alisivuston Virtuaalisivu' SubsiteField: Alisivu diff --git a/lang/hr.yml b/lang/hr.yml new file mode 100644 index 0000000..558c59d --- /dev/null +++ b/lang/hr.yml @@ -0,0 +1,80 @@ +hr: + ASSETADMIN: + SUBSITENOTICE: 'Direktoriji i datoteke kreirane u glavnom sajtu su dostupne svim podsajtovima.' + DomainNameField: + INVALID_DOMAIN: 'Netočan naziv domene' + FileSubsites: + AllSitesDropdownOpt: 'Svi sajtovi' + SubsiteFieldLabel: Podsajt + GridFieldAddFromTemplateButton: + AddFromTemplate: 'Dodaj novi iz predloška' + GroupSubsites: + ACCESSALL: 'Svi podsajtovi' + ACCESSONLY: 'Samo ovi podsajtovi' + ACCESSRADIOTITLE: 'Dodijeli ovoj grupi pristup za' + GlobalGroup: 'globalna grupa' + MANAGE_SUBSITES: 'Upravljaj podsajtove za grupe' + MANAGE_SUBSITES_HELP: 'Mogućnost limitiranja prava za grupu za jedan ili više podsajtova.' + SECURITYTABTITLE: Podsajtovi + LeftAndMainSubsites: + Saved: 'Spremljeno, molimo osvježite povezane stranice.' + LeftAndMain_Menu: + Hello: Pozdrav + LOGOUT: Odjava + SiteTreeSubsites: + CopyAction: Kopiraj + CopyToSubsite: 'Kopiraj stranicu u podsajt' + Subsite: + COPYSTRUCTURE: 'Kopiraj strukturu od:' + CopyMessage: 'Kreirana kopija od {title}' + CustomExtraMeta: 'Prilagođeni Meta tagovi' + CustomMetaDescription: Opis + CustomMetaKeywords: 'Ključne riječi' + CustomMetaTitle: Naslov + DOMAINSAVEFIRST: 'Možete dodati domene nakon prvog spremanja' + DomainsHeadline: 'Domene za ove podsajtove' + DomainsListTitle: Domene + IsPublicHeaderField: 'Aktivni podsajtovi' + NOTEMPLATE: 'Nema predloška' + PLURALNAME: Podsajtovi + PageTypeBlacklistField: 'Ne dopuštaj tipove stranica?' + SINGULARNAME: Podsajt + SiteConfigSubtitle: 'vaš slogan ovdje' + SiteConfigTitle: 'Naziv vašeg weba' + TabTitleConfig: Konfiguracija + ValidateTitle: 'Molimo dodajte "Naslov"' + SubsiteAdmin: + MENUTITLE: Podsajtovi + SubsiteDomain: + DOMAIN: Domena + DOMAIN_DESCRIPTION: 'Hostname ovog podsajta (bez protokola). Omogućava wildcard (*).' + IS_PRIMARY: 'Da li je glavna domena?' + PLURALNAME: 'Domene podsajtova' + PROTOCOL_AUTOMATIC: Automatsko + PROTOCOL_DESCRIPTION: 'Označi kao zadanu domenu za ovu podstranicu' + PROTOCOL_HTTP: 'http://' + PROTOCOL_HTTPS: 'https://' + Protocol: Protokol + SINGULARNAME: 'Domena podsajta' + SubsiteReportWrapper: + ReportDropdown: Sajtovi + SubsiteXHRController: + MENUTITLE: SubsiteXHRController + Subsites: + DefaultSiteFieldLabel: 'Zadani sajt' + DomainFieldLabel: Domena + IsPublicFieldLabel: 'Omogućni javni pristup' + LanguageFieldLabel: Jezik + MainSiteTitle: 'Glavni sajt' + PageTypeBlacklistFieldLabel: 'Crna lista tipova stranica' + PrimaryDomainFieldLabel: 'Glavna domena' + RedirectURLFieldLabel: 'Link preusmjeravanja' + ThemeFieldLabel: Tema + TitleFieldLabel: 'Naziv podsajta' + SubsitesVirtualPage: + DESCRIPTION: 'Prikazuje sadržaj stranice na drugom podsajtu' + PLURALNAME: 'Bazna stranica' + SINGULARNAME: 'Virtualna stranica podsajta' + SubsiteField: Podsajt + VirtualPage: + EDITCONTENT: 'Klikni ovdje za uređivanje sadržaja' diff --git a/lang/id.yml b/lang/id.yml index 52c3151..5ae82e6 100644 --- a/lang/id.yml +++ b/lang/id.yml @@ -1,16 +1,16 @@ id: - SubsiteAdmin: - MENUTITLE: Subsitus FileSubsites: SubsiteFieldLabel: Subsitus GroupSubsites: SECURITYTABTITLE: Subsitus Subsite: CustomMetaDescription: Deskripsi - CustomMetaKeywords: Kata kunci + CustomMetaKeywords: 'Kata kunci' CustomMetaTitle: Judul PLURALNAME: Subsitus SINGULARNAME: Subsitus + SubsiteAdmin: + MENUTITLE: Subsitus SubsiteReportWrapper: ReportDropdown: Situs Subsites: diff --git a/lang/ja.yml b/lang/ja.yml index 7eedf0d..e8ac3c1 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -1,17 +1,16 @@ ja: - SubsiteAdmin: - MENUTITLE: サブサイト GridFieldAddFromTemplateButton: AddFromTemplate: 'テンプレートから新しく追加' GroupSubsites: ACCESSALL: '全てのサブサイト' - ACCESSONLY: 'これらのサブサイトのみ' + ACCESSONLY: これらのサブサイトのみ ACCESSRADIOTITLE: 'このグループに選択先へのアクセス権を与える' SECURITYTABTITLE: サブサイト + SubsiteAdmin: + MENUTITLE: サブサイト SubsiteDomain: DOMAIN: ドメイン - IS_PRIMARY: 'プライマリドメイン' - PLURALNAME: 'サブサイトのドメイン' - SINGULARNAME: 'サブサイトのドメイン' + PLURALNAME: サブサイトのドメイン + SINGULARNAME: サブサイトのドメイン SubsitesVirtualPage: - SINGULARNAME: 'サブサイトの仮想ページ' + SINGULARNAME: サブサイトの仮想ページ diff --git a/lang/lt.yml b/lang/lt.yml new file mode 100644 index 0000000..92eb136 --- /dev/null +++ b/lang/lt.yml @@ -0,0 +1,45 @@ +lt: + FileSubsites: + AllSitesDropdownOpt: 'Visos svetainės' + GridFieldAddFromTemplateButton: + AddFromTemplate: 'Sukurti naują pagal šabloną' + LeftAndMainSubsites: + Saved: 'Išsaugota, prašome atnaujinti susijusius puslapius' + LeftAndMain_Menu: + Hello: Sveiki + LOGOUT: Atsijungti + SiteTreeSubsites: + CopyAction: Kopijuoti + CopyToSubsite: 'Kopijuoti puslapį į kitą svetainę' + Subsite: + COPYSTRUCTURE: 'Kopijuoti struktūrą iš:' + CopyMessage: 'Sukurta {title} kopija' + CustomExtraMeta: 'Kitos meta žymės' + CustomMetaDescription: Aprašymas + CustomMetaKeywords: Raktažodžiai + CustomMetaTitle: Pavadinimas + DomainsHeadline: 'Šio puslapio domenai' + DomainsListTitle: Domenai + NOTEMPLATE: 'Nėra šablono' + PageTypeBlacklistField: 'Neleidžiami puslapių tipai' + SiteConfigSubtitle: 'Jūsų svetainės šūkis' + SiteConfigTitle: 'Jūsų svetainės pavadinimas' + TabTitleConfig: Nustatymai + ValidateTitle: 'Prašome įvesti "Pavadinimą"' + SubsiteDomain: + DOMAIN: Domenas + SubsiteReportWrapper: + ReportDropdown: Svetainės + SubsiteXHRController: + MENUTITLE: SubsiteXHRController + Subsites: + DefaultSiteFieldLabel: 'Pagrindinė svetainė' + DomainFieldLabel: Domenas + IsPublicFieldLabel: 'Leisti pasiekti visiems' + LanguageFieldLabel: Kalba + MainSiteTitle: 'Pagrindinis puslapis' + PrimaryDomainFieldLabel: 'Pagrindinis domenas' + RedirectURLFieldLabel: 'Nukreipimo nuoroda' + ThemeFieldLabel: Tema + SubsitesVirtualPage: + PLURALNAME: 'Baziniai puslapiai' diff --git a/lang/mi.yml b/lang/mi.yml index 6b67cb7..ae74684 100644 --- a/lang/mi.yml +++ b/lang/mi.yml @@ -1,13 +1,9 @@ mi: - SubsiteAdmin: - MENUTITLE: Ngā pae iti - SubsiteXHRController: - MENUTITLE: ManaXHRPaeiti ASSETADMIN: SUBSITENOTICE: 'Ka taea ngā kōpaki me ngā kōnae kua hangaia i te pae matua te uru mā ngā pae iti katoa.' FileSubsites: AllSitesDropdownOpt: 'Ngā pae katoa' - SubsiteFieldLabel: Pae iti + SubsiteFieldLabel: 'Pae iti' GridFieldAddFromTemplateButton: AddFromTemplate: 'Tāpiri Hōu mai i te Tātauira' GroupSubsites: @@ -17,7 +13,7 @@ mi: GlobalGroup: 'Rōpū Hurinoa' MANAGE_SUBSITES: 'Whakahaere pae iti mō ngā rōpū' MANAGE_SUBSITES_HELP: 'Te āheinga ki te whakawhāiti whakaaetanga mō tētahi rōpū ki tētahi neke atu rānei o ngā pae iti.' - SECURITYTABTITLE: Ngā pae iti + SECURITYTABTITLE: 'Ngā pae iti' LeftAndMainSubsites: Saved: 'Kua tiakina, whakahoutia ngā whārangi pāhono.' SiteTreeSubsites: @@ -28,27 +24,30 @@ mi: CopyMessage: 'I hangaia he tārua o {title}' CustomExtraMeta: 'Ngā Tūtohu Meta Ritenga' CustomMetaDescription: Whakaahuatanga - CustomMetaKeywords: Ngā kupumatua + CustomMetaKeywords: 'Ngā kupumatua' CustomMetaTitle: Taitara DOMAINSAVEFIRST: 'Ka taea noa iho te tāpiri rohe i muri i te tiakinga tuatahitanga' DomainsHeadline: 'Ngā rohe mō tēnei pae iti' - DomainsListTitle: Ngā Rohe + DomainsListTitle: 'Ngā Rohe' IsPublicHeaderField: 'Pae iti hohe' NOTEMPLATE: 'Kāore he tātauira' - PLURALNAME: Ngā pae iti + PLURALNAME: 'Ngā pae iti' PageTypeBlacklistField: 'Me whakakāhore ngā momo whārangi?' - SINGULARNAME: Pae iti + SINGULARNAME: 'Pae iti' SiteConfigSubtitle: 'Tō rārangi tautuhinga ki konei' SiteConfigTitle: 'Tō Ingoa Pae' TabTitleConfig: Whirihoranga ValidateTitle: 'Tāurua he "Taitara"' + SubsiteAdmin: + MENUTITLE: 'Ngā pae iti' SubsiteDomain: DOMAIN: Rohe - IS_PRIMARY: 'Ko Te Rohe Matua' PLURALNAME: 'Ngā Rohe Pae Iti' SINGULARNAME: 'Rohe Pae Iti' SubsiteReportWrapper: - ReportDropdown: Ngā Pae + ReportDropdown: 'Ngā Pae' + SubsiteXHRController: + MENUTITLE: ManaXHRPaeiti Subsites: DefaultSiteFieldLabel: 'Pae taunoa' DomainFieldLabel: Rohe @@ -62,6 +61,5 @@ mi: TitleFieldLabel: 'Ingoa Pae Iti' SubsitesVirtualPage: DESCRIPTION: 'Ka whakaatu i ngā ihirangi o tētahi whārangi ki tētahi atu pae iti' - PLURALNAME: 'Ngā Whārangi Taketake' SINGULARNAME: 'Whārangi Mariko Pae Iti' - SubsiteField: Pae iti + SubsiteField: 'Pae iti' diff --git a/lang/nb_NO.yml b/lang/nb_NO.yml index dbee77f..4522c69 100644 --- a/lang/nb_NO.yml +++ b/lang/nb_NO.yml @@ -1,6 +1,4 @@ nb_NO: - SubsiteAdmin: - MENUTITLE: Underdomener GridFieldAddFromTemplateButton: AddFromTemplate: 'Add New from Template' GroupSubsites: @@ -8,9 +6,10 @@ nb_NO: ACCESSONLY: 'Only these subsites' ACCESSRADIOTITLE: 'Give this group access to' SECURITYTABTITLE: subdomener + SubsiteAdmin: + MENUTITLE: Underdomener SubsiteDomain: DOMAIN: Domain - IS_PRIMARY: 'Is Primary Domain' PLURALNAME: 'Subsite Domains' SINGULARNAME: 'Subsite Domain' SubsitesVirtualPage: diff --git a/lang/nl.yml b/lang/nl.yml index aef2937..3f30468 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -1,6 +1,4 @@ nl: - SubsiteAdmin: - MENUTITLE: Subsites FileSubsites: SubsiteFieldLabel: Subsite GridFieldAddFromTemplateButton: @@ -17,9 +15,10 @@ nl: SINGULARNAME: Subsite SiteConfigTitle: 'Jouw Site Naam' TabTitleConfig: Configuratie + SubsiteAdmin: + MENUTITLE: Subsites SubsiteDomain: DOMAIN: Domein - IS_PRIMARY: 'Is Primaire domein' PLURALNAME: 'Subsite Domeinen' SINGULARNAME: 'Subsite Domein' Subsites: @@ -29,6 +28,5 @@ nl: ThemeFieldLabel: Thema TitleFieldLabel: 'Subsite naam' SubsitesVirtualPage: - PLURALNAME: 'Subsites Virtuele pagina' SINGULARNAME: 'Subsites Virtuele pagina' SubsiteField: Subsite diff --git a/lang/pl_PL.yml b/lang/pl_PL.yml index dc57ca0..5fd0b19 100644 --- a/lang/pl_PL.yml +++ b/lang/pl_PL.yml @@ -1,6 +1,4 @@ pl_PL: - SubsiteAdmin: - MENUTITLE: Podwitryny GridFieldAddFromTemplateButton: AddFromTemplate: 'Dodaj nową na podstawie szablonu' GroupSubsites: @@ -8,9 +6,10 @@ pl_PL: ACCESSONLY: 'Tylko te podwitryny' ACCESSRADIOTITLE: 'Daj tej grupie dostęp do' SECURITYTABTITLE: Podwitryny + SubsiteAdmin: + MENUTITLE: Podwitryny SubsiteDomain: DOMAIN: Domena - IS_PRIMARY: 'Ma priorytet?' PLURALNAME: 'Domeny podwitryny' SINGULARNAME: 'Domena podwitryny' SubsitesVirtualPage: diff --git a/lang/ru.yml b/lang/ru.yml new file mode 100644 index 0000000..fa9223e --- /dev/null +++ b/lang/ru.yml @@ -0,0 +1,82 @@ +ru: + ASSETADMIN: + SUBSITENOTICE: 'Папки и файлы созданные на основном сайте так же доступны для под-сайтов.' + DomainNameField: + INVALID_DOMAIN: 'Неверный домен' + FileSubsites: + AllSitesDropdownOpt: 'Все сайты' + SubsiteFieldLabel: Подсайт + GridFieldAddFromTemplateButton: + AddFromTemplate: 'Добавить из шаблона' + GroupSubsites: + ACCESSALL: 'Все подсайты' + ACCESSONLY: 'Только эти подсайты' + ACCESSRADIOTITLE: 'Разрешить этой группе доступ к' + GlobalGroup: 'глобальная группа' + MANAGE_SUBSITES: 'Управление подсайтами для групп' + MANAGE_SUBSITES_HELP: 'Возможность ограничить права доступа для группы к одному и более подсайтам' + SECURITYTABTITLE: Подсайты + LeftAndMainSubsites: + Saved: 'Сохранено, пожалуйста обновите связанные группы' + LeftAndMain_Menu: + Hello: Здравствуйте + LOGOUT: 'Выход' + SiteTreeSubsites: + CopyAction: Копировать + CopyToSubsite: 'Копировать страницу на подсайт' + CopyToSubsiteWithChildren: 'Включая под-страницы?' + SubsiteOperations: 'Операции над подсайтами' + Subsite: + COPYSTRUCTURE: 'Скопировать структуры из:' + CopyMessage: 'Создана копия {title}' + CustomExtraMeta: 'Пользовательские мета-тэги' + CustomMetaDescription: Описание + CustomMetaKeywords: 'Ключевые слова' + CustomMetaTitle: Заголовок + DOMAINSAVEFIRST: 'Вы можете добавлять домены только после первого сохранения' + DomainsHeadline: 'Домены для этого подсайта' + DomainsListTitle: Домены + IsPublicHeaderField: 'Активный подсайт' + NOTEMPLATE: 'Нет шаблона' + PLURALNAME: Подсайты + PageTypeBlacklistField: 'Запретить данные типы страниц?' + SINGULARNAME: Подсайт + SiteConfigSubtitle: 'ваш слоган здесь' + SiteConfigTitle: 'Название сайта' + TabTitleConfig: Конфигурация + ValidateTitle: 'Пожалуйста, добавьте "Заголовок"' + SubsiteAdmin: + MENUTITLE: Подсайты + SubsiteDomain: + DOMAIN: Домен + DOMAIN_DESCRIPTION: 'Домен подсайта (без указания протоколов: http:// и https://). Разрешены (*) для обозначения поддоменов.' + IS_PRIMARY: 'Это основной домен?' + PLURALNAME: 'Домены подсайтов' + PROTOCOL_AUTOMATIC: Автоматически + PROTOCOL_DESCRIPTION: 'Отметить как основной домен для данного подсайта' + PROTOCOL_HTTP: 'http://' + PROTOCOL_HTTPS: 'https://' + Protocol: Протокол + SINGULARNAME: 'Домен подсайта' + SubsiteReportWrapper: + ReportDropdown: Сайты + SubsiteXHRController: + MENUTITLE: SubsiteXHRController + Subsites: + DefaultSiteFieldLabel: 'Основной сайт' + DomainFieldLabel: Домен + IsPublicFieldLabel: 'Разрешить публичный доступ' + LanguageFieldLabel: Язык + MainSiteTitle: 'Основной Сайт' + PageTypeBlacklistFieldLabel: 'Запрещённые типы страниц' + PrimaryDomainFieldLabel: 'Основной Домен' + RedirectURLFieldLabel: 'Ссылка для перенаправления' + ThemeFieldLabel: Оформление + TitleFieldLabel: 'Название Подсайта' + SubsitesVirtualPage: + DESCRIPTION: 'Отображает содержимое выбранной страницы на другом подсайте' + PLURALNAME: 'Базовые страницы' + SINGULARNAME: 'Виртуальная страница подсайта' + SubsiteField: Подсайт + VirtualPage: + EDITCONTENT: 'Нажмите для изменения содержимого' diff --git a/lang/tr_TR.yml b/lang/tr_TR.yml index c515f0a..29c51f9 100644 --- a/lang/tr_TR.yml +++ b/lang/tr_TR.yml @@ -1,16 +1,15 @@ tr_TR: - SubsiteAdmin: - MENUTITLE: Alt Siteler GridFieldAddFromTemplateButton: AddFromTemplate: 'Add New from Template' GroupSubsites: ACCESSALL: 'All subsites' ACCESSONLY: 'Only these subsites' ACCESSRADIOTITLE: 'Give this group access to' - SECURITYTABTITLE: Alt Siteler + SECURITYTABTITLE: 'Alt Siteler' + SubsiteAdmin: + MENUTITLE: 'Alt Siteler' SubsiteDomain: DOMAIN: Domain - IS_PRIMARY: 'Is Primary Domain' PLURALNAME: 'Subsite Domains' SINGULARNAME: 'Subsite Domain' SubsitesVirtualPage: diff --git a/lang/zh.yml b/lang/zh.yml index 6af6b7b..86bc366 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -1,67 +1,65 @@ zh: - SubsiteAdmin: - MENUTITLE: 多个子网站 - SubsiteXHRController: - MENUTITLE: 子网站 XHR 控制器 ASSETADMIN: - SUBSITENOTICE: '主网站上创建的文件夹和文件可以被所有子网站访问。' + SUBSITENOTICE: 主网站上创建的文件夹和文件可以被所有子网站访问。 FileSubsites: - AllSitesDropdownOpt: '所有网站' + AllSitesDropdownOpt: 所有网站 SubsiteFieldLabel: 子网站 GridFieldAddFromTemplateButton: AddFromTemplate: '从模板中取出新加入' GroupSubsites: - ACCESSALL: '所有子网站' + ACCESSALL: 所有子网站 ACCESSONLY: '仅这些子网站' ACCESSRADIOTITLE: '准许该群进入' GlobalGroup: '全局小组' - MANAGE_SUBSITES: '管理小组的子网站' - MANAGE_SUBSITES_HELP: '能够将权限限制在一个小组、一个或多个子网站。' + MANAGE_SUBSITES: 管理小组的子网站 + MANAGE_SUBSITES_HELP: 能够将权限限制在一个小组、一个或多个子网站。 SECURITYTABTITLE: 多个子网站 LeftAndMainSubsites: Saved: '已保存,请更新相关的页面。' SiteTreeSubsites: CopyAction: 复制 - CopyToSubsite: '将页面复制到子网站' + CopyToSubsite: 将页面复制到子网站 Subsite: - COPYSTRUCTURE: '复制结构来自:' + COPYSTRUCTURE: 复制结构来自: CopyMessage: '已创建一个 {title} 的副本' CustomExtraMeta: '自定义 Meta 标签' CustomMetaDescription: 说明 - CustomMetaKeywords: 关键词 - CustomMetaTitle: 标题 + CustomMetaKeywords: '关键词' + CustomMetaTitle: '标题' DOMAINSAVEFIRST: '只有当您第一次保存后,才能添加域名' - DomainsHeadline: '这个子网站的域名' + DomainsHeadline: 这个子网站的域名 DomainsListTitle: 域名 - IsPublicHeaderField: '活跃的子网站' - NOTEMPLATE: '没有模板' + IsPublicHeaderField: 活跃的子网站 + NOTEMPLATE: 没有模板 PLURALNAME: 多个子网站 - PageTypeBlacklistField: '禁止页面类型?' + PageTypeBlacklistField: 禁止页面类型? SINGULARNAME: 子网站 SiteConfigSubtitle: '您的标语在这里' - SiteConfigTitle: '您的网站名称' - TabTitleConfig: 配置 + SiteConfigTitle: 您的网站名称 + TabTitleConfig: '配置' ValidateTitle: '请添加一个“标题”' + SubsiteAdmin: + MENUTITLE: 多个子网站 SubsiteDomain: DOMAIN: 域名 - IS_PRIMARY: '是主域名' - PLURALNAME: '多个子网站域名' - SINGULARNAME: '子网站域名' + PLURALNAME: 多个子网站域名 + SINGULARNAME: 子网站域名 SubsiteReportWrapper: ReportDropdown: 网站 + SubsiteXHRController: + MENUTITLE: '子网站 XHR 控制器' Subsites: - DefaultSiteFieldLabel: '默认网站' + DefaultSiteFieldLabel: 默认网站 DomainFieldLabel: 域名 IsPublicFieldLabel: '启用公共访问权' LanguageFieldLabel: 语言 - MainSiteTitle: '主网站' - PageTypeBlacklistFieldLabel: '页面类型黑名单' - PrimaryDomainFieldLabel: '主域名' + MainSiteTitle: 主网站 + PageTypeBlacklistFieldLabel: 页面类型黑名单 + PrimaryDomainFieldLabel: 主域名 RedirectURLFieldLabel: '重定向 URL' ThemeFieldLabel: 主题 - TitleFieldLabel: '子网站名称' + TitleFieldLabel: 子网站名称 SubsitesVirtualPage: DESCRIPTION: '显示另一个子网站上一个页面的内容' - PLURALNAME: '基本页面' - SINGULARNAME: '子网站虚拟页面' + SINGULARNAME: 子网站虚拟页面 SubsiteField: 子网站 diff --git a/templates/LeftAndMain_Menu.ss b/templates/LeftAndMain_Menu.ss new file mode 100644 index 0000000..9eeec85 --- /dev/null +++ b/templates/LeftAndMain_Menu.ss @@ -0,0 +1,45 @@ +
+
+ + + + <% if $ListSubsites %> + <% include SubsiteList %> + <% end_if %> +
+ +
+ +
+ +
+ + auto + » + « +
+
diff --git a/tests/BaseSubsiteTest.php b/tests/BaseSubsiteTest.php index 82109c2..007a37a 100644 --- a/tests/BaseSubsiteTest.php +++ b/tests/BaseSubsiteTest.php @@ -6,31 +6,28 @@ use SilverStripe\Subsites\Model\Subsite; class BaseSubsiteTest extends SapphireTest { - function setUp() + public function setUp() { parent::setUp(); - Subsite::$use_session_subsiteid = true; - } + Subsite::$use_session_subsiteid = true; + Subsite::$force_subsite = null;} - /** - * Avoid subsites filtering on fixture fetching. - */ - function objFromFixture($class, $id) - { - Subsite::disable_subsite_filter(true); - $obj = parent::objFromFixture($class, $id); - Subsite::disable_subsite_filter(false); + /** + * Avoid subsites filtering on fixture fetching. + */ + public function objFromFixture($class, $id) + {Subsite::disable_subsite_filter(true); + $obj = parent::objFromFixture($class, $id); + Subsite::disable_subsite_filter(false); return $obj; } - /** - * Tests the initial state of disable_subsite_filter - */ - function testDisableSubsiteFilter() - { - $this->assertFalse(Subsite::$disable_subsite_filter); - } - + /** + * Tests the initial state of disable_subsite_filter + */ + public function testDisableSubsiteFilter() + {$this->assertFalse(Subsite::$disable_subsite_filter); + } } diff --git a/tests/FileSubsitesTest.php b/tests/FileSubsitesTest.php index 883bc6a..e6e4d52 100644 --- a/tests/FileSubsitesTest.php +++ b/tests/FileSubsitesTest.php @@ -3,33 +3,47 @@ use SilverStripe\Assets\File; use SilverStripe\Assets\Folder; use SilverStripe\Forms\FieldList; -use SilverStripe\Subsites\Extensions\FileSubsites; +use SilverStripe\Security\Member; use SilverStripe\Subsites\Model\Subsite; - class FileSubsitesTest extends BaseSubsiteTest { - static $fixture_file = 'subsites/tests/SubsiteTest.yml'; + public static $fixture_file = 'subsites/tests/SubsiteTest.yml'; - function testTrivialFeatures() + /** + * Disable other file extensions + * + * @var array + */ + protected $illegalExtensions = array( + 'File' => array( + 'SecureFileExtension', + 'VersionedFileExtension' + ), + 'SiteTree' => array( + 'Translatable', + ) + ); + + public function testTrivialFeatures() { - $this->assertTrue(is_array(singleton(FileSubsites::class)->extraStatics())); + $this->assertTrue(is_array(singleton('FileSubsites')->extraStatics())); $file = new File(); $file->Name = 'FileTitle'; $file->Title = 'FileTitle'; $this->assertEquals(' * FileTitle', $file->alternateTreeTitle()); - $file->SubsiteID = $this->objFromFixture(Subsite::class, 'domaintest1')->ID; + $file->SubsiteID = $this->objFromFixture('Subsite', 'domaintest1')->ID; $this->assertEquals('FileTitle', $file->getTreeTitle()); - $this->assertTrue(singleton('SilverStripe\\Assets\\Folder')->getCMSFields() instanceof FieldList); + $this->assertTrue(singleton(Folder::class)->getCMSFields() instanceof FieldList); Subsite::changeSubsite(1); $this->assertEquals($file->cacheKeyComponent(), 'subsite-1'); } - function testWritingSubsiteID() + public function testWritingSubsiteID() { - $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')->logIn(); + $this->objFromFixture(Member::class, 'admin')->logIn(); - $subsite = $this->objFromFixture(Subsite::class, 'domaintest1'); + $subsite = $this->objFromFixture('Subsite', 'domaintest1'); FileSubsites::$default_root_folders_global = true; Subsite::changeSubsite(0); @@ -64,24 +78,25 @@ class FileSubsitesTest extends BaseSubsiteTest $this->assertEquals($folder->SubsiteID, $file->SubsiteID); } - function testSubsitesFolderDropdown() + public function testSubsitesFolderDropdown() { - $this->objFromFixture('SilverStripe\\Security\\Member', 'admin')->logIn(); + $this->objFromFixture(Member::class, 'admin')->logIn(); $file = new Folder(); $source = array_values($file->getCMSFields()->dataFieldByName('SubsiteID')->getSource()); asort($source); - $this->assertEquals([ + $this->assertEquals(array( 'Main site', - 'Template', 'Subsite1 Template', 'Subsite2 Template', + 'Template', 'Test 1', 'Test 2', - 'Test 3' - ], $source); + 'Test 3', + 'Test Non-SSL', + 'Test SSL', + ), array_values($source)); } - } diff --git a/tests/GroupSubsitesTest.php b/tests/GroupSubsitesTest.php index f7ca5d3..2799522 100644 --- a/tests/GroupSubsitesTest.php +++ b/tests/GroupSubsitesTest.php @@ -1,25 +1,24 @@ assertTrue(is_array(singleton(GroupSubsites::class)->extraStatics())); - $this->assertTrue(is_array(singleton(GroupSubsites::class)->providePermissions())); - $this->assertTrue(singleton('SilverStripe\\Security\\Group')->getCMSFields() instanceof FieldList); + $this->assertTrue(is_array(singleton('GroupSubsites')->extraStatics())); + $this->assertTrue(is_array(singleton('GroupSubsites')->providePermissions())); + $this->assertTrue(singleton(Group::class)->getCMSFields() instanceof FieldList); } - function testAlternateTreeTitle() + public function testAlternateTreeTitle() { $group = new Group(); $group->Title = 'The A Team'; diff --git a/tests/LeftAndMainSubsitesTest.php b/tests/LeftAndMainSubsitesTest.php index 9f38e8b..69deb7c 100644 --- a/tests/LeftAndMainSubsitesTest.php +++ b/tests/LeftAndMainSubsitesTest.php @@ -1,21 +1,21 @@ objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); + $member = $this->objFromFixture(Member::class, 'subsite1member'); - $cmsmain = singleton('SilverStripe\\CMS\\Controllers\\CMSMain'); + $cmsmain = singleton(CMSMain::class); $subsites = $cmsmain->sectionSites(true, "Main site", $member); - $this->assertDOSEquals([ - ['Title' => 'Subsite1 Template'] - ], $subsites, 'Lists member-accessible sites for the accessible controller.'); + $this->assertDOSEquals(array( + array('Title' =>'Subsite1 Template') + ), $subsites, 'Lists member-accessible sites for the accessible controller.'); $assetadmin = singleton(AssetAdmin::class); $subsites = $assetadmin->sectionSites(true, "Main site", $member); $this->assertDOSEquals([], $subsites, 'Does not list any sites for forbidden controller.'); - $member = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); + $member = $this->objFromFixture(Member::class, 'editor'); - $cmsmain = singleton('SilverStripe\\CMS\\Controllers\\CMSMain'); + $cmsmain = singleton(CMSMain::class); $subsites = $cmsmain->sectionSites(true, "Main site", $member); - $this->assertDOSContains([ - ['Title' => 'Main site'] - ], $subsites, 'Includes the main site for members who can access all sites.'); + $this->assertDOSContains(array( + array('Title' =>'Main site') + ), $subsites, 'Includes the main site for members who can access all sites.'); } - function testAccessChecksDontChangeCurrentSubsite() + public function testAccessChecksDontChangeCurrentSubsite() { - $admin = $this->objFromFixture("SilverStripe\\Security\\Member", "admin"); + $admin = $this->objFromFixture(Member::class, "admin"); $this->loginAs($admin); - $ids = []; + $ids = array(); $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); $subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2'); @@ -69,34 +69,31 @@ class LeftAndMainSubsitesTest extends FunctionalTest Subsite::changeSubsite($id); $this->assertEquals($id, Subsite::currentSubsiteID()); - $left = new LeftAndMain(); - $this->assertTrue($left->canView(), "Admin user can view subsites LeftAndMain with id = '$id'"); - $this->assertEquals($id, Subsite::currentSubsiteID(), - "The current subsite has not been changed in the process of checking permissions for admin user."); - } + $left = new LeftAndMain(); + $this->assertTrue($left->canView(), "Admin user can view subsites LeftAndMain with id = '$id'"); + $this->assertEquals($id, Subsite::currentSubsiteID(), + "The current subsite has not been changed in the process of checking permissions for admin user."); + } - } + } - function testShouldChangeSubsite() + public function testShouldChangeSubsite() { $l = new LeftAndMain(); Config::inst()->nest(); - Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', - false); - $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 0)); - $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 1)); + Config::inst()->update(CMSPageEditController::class, 'treats_subsite_0_as_global', false); + $this->assertTrue($l->shouldChangeSubsite(CMSPageEditController::class, 0, 5)); + $this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 0, 0)); + $this->assertTrue($l->shouldChangeSubsite(CMSPageEditController::class, 1, 5)); + $this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 1, 1)); - Config::modify()->set('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 'treats_subsite_0_as_global', - true); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 0, 0)); - $this->assertTrue($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 5)); - $this->assertFalse($l->shouldChangeSubsite('SilverStripe\\CMS\\Controllers\\CMSPageEditController', 1, 1)); + Config::inst()->update(CMSPageEditController::class, 'treats_subsite_0_as_global', true); + $this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 0, 5)); + $this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 0, 0)); + $this->assertTrue($l->shouldChangeSubsite(CMSPageEditController::class, 1, 5)); + $this->assertFalse($l->shouldChangeSubsite(CMSPageEditController::class, 1, 1)); Config::inst()->unnest(); } - } diff --git a/tests/SiteConfigSubsitesTest.php b/tests/SiteConfigSubsitesTest.php index d1a933e..bfc9afc 100644 --- a/tests/SiteConfigSubsitesTest.php +++ b/tests/SiteConfigSubsitesTest.php @@ -14,32 +14,31 @@ class SiteConfigSubsitesTest extends BaseSubsiteTest $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); $subsite2 = $this->objFromFixture(Subsite::class, 'domaintest2'); - $this->assertTrue(is_array(singleton(SiteConfigSubsites::class)->extraStatics())); + $this->assertTrue(is_array(singleton(SiteConfigSubsites::class)->extraStatics())); - Subsite::changeSubsite(0); - $sc = SiteConfig::current_site_config(); - $sc->Title = 'RootSite'; - $sc->write(); + Subsite::changeSubsite(0); + $sc = SiteConfig::current_site_config(); + $sc->Title = 'RootSite'; + $sc->write(); - Subsite::changeSubsite($subsite1->ID); - $sc = SiteConfig::current_site_config(); - $sc->Title = 'Subsite1'; - $sc->write(); + Subsite::changeSubsite($subsite1->ID); + $sc = SiteConfig::current_site_config(); + $sc->Title = 'Subsite1'; + $sc->write(); - Subsite::changeSubsite($subsite2->ID); - $sc = SiteConfig::current_site_config(); - $sc->Title = 'Subsite2'; - $sc->write(); + Subsite::changeSubsite($subsite2->ID); + $sc = SiteConfig::current_site_config(); + $sc->Title = 'Subsite2'; + $sc->write(); - Subsite::changeSubsite(0); - $this->assertEquals(SiteConfig::current_site_config()->Title, 'RootSite'); - Subsite::changeSubsite($subsite1->ID); - $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite1'); - Subsite::changeSubsite($subsite2->ID); - $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite2'); + Subsite::changeSubsite(0); + $this->assertEquals(SiteConfig::current_site_config()->Title, 'RootSite'); + Subsite::changeSubsite($subsite1->ID); + $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite1'); + Subsite::changeSubsite($subsite2->ID); + $this->assertEquals(SiteConfig::current_site_config()->Title, 'Subsite2'); $keys = SiteConfig::current_site_config()->extend('cacheKeyComponent'); $this->assertContains('subsite-' . $subsite2->ID, $keys); } - } diff --git a/tests/SiteTreeSubsitesTest.php b/tests/SiteTreeSubsitesTest.php index 40027d0..bbd48f7 100644 --- a/tests/SiteTreeSubsitesTest.php +++ b/tests/SiteTreeSubsitesTest.php @@ -1,13 +1,17 @@ ['Translatable'] + SiteTree::class => ['Translatable'] ]; - function testPagesInDifferentSubsitesCanShareURLSegment() - { + public function testPagesInDifferentSubsitesCanShareURLSegment() { $subsiteMain = $this->objFromFixture(Subsite::class, 'main'); $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); @@ -57,82 +60,80 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest $this->assertEquals($pageMain->URLSegment, $pageSubsite1->URLSegment, 'Pages in different subsites can share the same URL' ); - } + } - function testBasicSanity() - { - $this->assertTrue(singleton('SilverStripe\\CMS\\Model\\SiteTree')->getSiteConfig() instanceof SiteConfig); + public function testBasicSanity() { + $this->assertTrue(singleton(SiteTree::class)->getSiteConfig() instanceof SiteConfig); // The following assert is breaking in Translatable. - $this->assertTrue(singleton('SilverStripe\\CMS\\Model\\SiteTree')->getCMSFields() instanceof FieldList); + $this->assertTrue(singleton(SiteTree::class)->getCMSFields() instanceof FieldList); $this->assertTrue(singleton(SubsitesVirtualPage::class)->getCMSFields() instanceof FieldList); $this->assertTrue(is_array(singleton(SiteTreeSubsites::class)->extraStatics())); } - function testErrorPageLocations() + public function testErrorPageLocations() { + $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); + + Subsite::changeSubsite($subsite1->ID); + $path = ErrorPage::get_filepath_for_errorcode(500); + + $static_path = Config::inst()->get(ErrorPage::class, 'static_filepath'); + $expected_path = $static_path . '/error-500-'.$subsite1->domain().'.html'; + $this->assertEquals($expected_path, $path); + } + + public function testCanEditSiteTree() { - $subsite1 = $this->objFromFixture(Subsite::class, 'domaintest1'); - - Subsite::changeSubsite($subsite1->ID); - $path = SiteTreeSubsitesTest_ErrorPage::get_error_filename_spy(500); - - $expected_path = 'error-500-' . $subsite1->domain() . '.html'; - $this->assertEquals($expected_path, $path); - } - - function testCanEditSiteTree() - { - $admin = $this->objFromFixture('SilverStripe\\Security\\Member', 'admin'); - $subsite1member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite1member'); - $subsite2member = $this->objFromFixture('SilverStripe\\Security\\Member', 'subsite2member'); + $admin = $this->objFromFixture(Member::class, 'admin'); + $subsite1member = $this->objFromFixture(Member::class, 'subsite1member'); + $subsite2member = $this->objFromFixture(Member::class, 'subsite2member'); $mainpage = $this->objFromFixture('Page', 'home'); $subsite1page = $this->objFromFixture('Page', 'subsite1_home'); $subsite2page = $this->objFromFixture('Page', 'subsite2_home'); $subsite1 = $this->objFromFixture(Subsite::class, 'subsite1'); $subsite2 = $this->objFromFixture(Subsite::class, 'subsite2'); - // Cant pass member as arguments to canEdit() because of GroupSubsites - Session::set("loggedInAs", $admin->ID); - $this->assertTrue( - (bool)$subsite1page->canEdit(), - 'Administrators can edit all subsites' - ); + // Cant pass member as arguments to canEdit() because of GroupSubsites + Session::set("loggedInAs", $admin->ID); + $this->assertTrue( + (bool)$subsite1page->canEdit(), + 'Administrators can edit all subsites' + ); - // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state - Subsite::changeSubsite($subsite1); + // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state + Subsite::changeSubsite($subsite1); - Session::set("loggedInAs", $subsite1member->ID); - $this->assertTrue( - (bool)$subsite1page->canEdit(), - 'Members can edit pages on a subsite if they are in a group belonging to this subsite' - ); + Session::set("loggedInAs", $subsite1member->ID); + $this->assertTrue( + (bool)$subsite1page->canEdit(), + 'Members can edit pages on a subsite if they are in a group belonging to this subsite' + ); - Session::set("loggedInAs", $subsite2member->ID); - $this->assertFalse( - (bool)$subsite1page->canEdit(), - 'Members cant edit pages on a subsite if they are not in a group belonging to this subsite' - ); + Session::set("loggedInAs", $subsite2member->ID); + $this->assertFalse( + (bool)$subsite1page->canEdit(), + 'Members cant edit pages on a subsite if they are not in a group belonging to this subsite' + ); - // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state - Subsite::changeSubsite(0); - $this->assertFalse( - $mainpage->canEdit(), - 'Members cant edit pages on the main site if they are not in a group allowing this' - ); - } + // @todo: Workaround because GroupSubsites->augmentSQL() is relying on session state + Subsite::changeSubsite(0); + $this->assertFalse( + $mainpage->canEdit(), + 'Members cant edit pages on the main site if they are not in a group allowing this' + ); + } - /** - * Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}. - */ - function testTwoPagesWithSameURLOnDifferentSubsites() - { - // Set up a couple of pages with the same URL on different subsites + /** + * Similar to {@link SubsitesVirtualPageTest->testSubsiteVirtualPageCanHaveSameUrlsegmentAsOtherSubsite()}. + */ + public function testTwoPagesWithSameURLOnDifferentSubsites() { + // Set up a couple of pages with the same URL on different subsites $s1 = $this->objFromFixture(Subsite::class, 'domaintest1'); $s2 = $this->objFromFixture(Subsite::class, 'domaintest2'); - $p1 = new SiteTree(); - $p1->Title = $p1->URLSegment = "test-page"; - $p1->SubsiteID = $s1->ID; - $p1->write(); + $p1 = new SiteTree(); + $p1->Title = $p1->URLSegment = "test-page"; + $p1->SubsiteID = $s1->ID; + $p1->write(); $p2 = new SiteTree(); $p2->Title = $p1->URLSegment = "test-page"; @@ -147,82 +148,182 @@ class SiteTreeSubsitesTest extends BaseSubsiteTest Subsite::changeSubsite($s1); $this->assertEquals($p1->ID, SiteTree::get_by_link('test-page')->ID); - Subsite::changeSubsite($s2); - $this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID); - } + Subsite::changeSubsite($s2); + $this->assertEquals($p2->ID, SiteTree::get_by_link('test-page')->ID); + } - function testPageTypesBlacklistInClassDropdown() - { - $editor = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); - Session::set("loggedInAs", $editor->ID); + public function testPageTypesBlacklistInClassDropdown() { + $editor = $this->objFromFixture(Member::class, 'editor'); + Session::set("loggedInAs", $editor->ID); $s1 = $this->objFromFixture(Subsite::class, 'domaintest1'); $s2 = $this->objFromFixture(Subsite::class, 'domaintest2'); - $page = singleton('SilverStripe\\CMS\\Model\\SiteTree'); + $page = singleton('SiteTree'); - $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; - $s1->write(); + $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; + $s1->write(); - Subsite::changeSubsite($s1); - $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); + Subsite::changeSubsite($s1); + $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); - $this->assertArrayNotHasKey('SilverStripe\\CMS\\Model\\ErrorPage', - $settingsFields - ); - $this->assertArrayNotHasKey('SiteTreeSubsitesTest_ClassA', - $settingsFields - ); - $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', - $settingsFields - ); + $this->assertArrayNotHasKey(ErrorPage::class, + $settingsFields + ); + $this->assertArrayNotHasKey('SiteTreeSubsitesTest_ClassA', + $settingsFields + ); + $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', + $settingsFields + ); - Subsite::changeSubsite($s2); - $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); - $this->assertArrayHasKey('SilverStripe\\CMS\\Model\\ErrorPage', - $settingsFields - ); - $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassA', - $settingsFields - ); - $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', - $settingsFields - ); + Subsite::changeSubsite($s2); + $settingsFields = $page->getSettingsFields()->dataFieldByName('ClassName')->getSource(); + $this->assertArrayHasKey(ErrorPage::class, + $settingsFields + ); + $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassA', + $settingsFields + ); + $this->assertArrayHasKey('SiteTreeSubsitesTest_ClassB', + $settingsFields + ); + } + + public function testCopyToSubsite() { + // Remove baseurl if testing in subdir + Config::inst()->update(Director::class, 'alternate_base_url', '/'); + + /** @var Subsite $otherSubsite */ + $otherSubsite = $this->objFromFixture(Subsite::class, 'subsite1'); + $staffPage = $this->objFromFixture('Page', 'staff'); // nested page + $contactPage = $this->objFromFixture('Page', 'contact'); // top level page + + $staffPage2 = $staffPage->duplicateToSubsite($otherSubsite->ID); + $contactPage2 = $contactPage->duplicateToSubsite($otherSubsite->ID); + + $this->assertNotEquals($staffPage->ID, $staffPage2->ID); + $this->assertNotEquals($staffPage->SubsiteID, $staffPage2->SubsiteID); + $this->assertNotEquals($contactPage->ID, $contactPage2->ID); + $this->assertNotEquals($contactPage->SubsiteID, $contactPage2->SubsiteID); + $this->assertEmpty($staffPage2->ParentID); + $this->assertEmpty($contactPage2->ParentID); + $this->assertNotEmpty($staffPage->ParentID); + $this->assertEmpty($contactPage->ParentID); + + // Staff is shifted to top level and given a unique url segment + $domain = $otherSubsite->domain(); + $this->assertEquals('http://'.$domain.'/staff-2/', $staffPage2->AbsoluteLink()); + $this->assertEquals('http://'.$domain.'/contact-us-2/', $contactPage2->AbsoluteLink()); } - function testPageTypesBlacklistInCMSMain() - { - $editor = $this->objFromFixture('SilverStripe\\Security\\Member', 'editor'); - Session::set("loggedInAs", $editor->ID); + public function testPageTypesBlacklistInCMSMain() { + $editor = $this->objFromFixture('Member', 'editor'); + Session::set("loggedInAs", $editor->ID); - $cmsmain = new CMSMain(); + $cmsmain = new CMSMain(); - $s1 = $this->objFromFixture(Subsite::class, 'domaintest1'); - $s2 = $this->objFromFixture(Subsite::class, 'domaintest2'); + $s1 = $this->objFromFixture(Subsite::class,'domaintest1'); + $s2 = $this->objFromFixture(Subsite::class,'domaintest2'); - $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; - $s1->write(); + $s1->PageTypeBlacklist = 'SiteTreeSubsitesTest_ClassA,ErrorPage'; + $s1->write(); - Subsite::changeSubsite($s1); - $hints = Convert::json2array($cmsmain->SiteTreeHints()); - $classes = $hints['Root']['disallowedChildren']; - $this->assertContains('SilverStripe\\CMS\\Model\\ErrorPage', $classes); - $this->assertContains('SiteTreeSubsitesTest_ClassA', $classes); - $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); + Subsite::changeSubsite($s1); + $hints = Convert::json2array($cmsmain->SiteTreeHints()); + $classes = $hints['Root']['disallowedChildren']; + $this->assertContains(ErrorPage::class, $classes); + $this->assertContains('SiteTreeSubsitesTest_ClassA', $classes); + $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); - Subsite::changeSubsite($s2); - $hints = Convert::json2array($cmsmain->SiteTreeHints()); - $classes = $hints['Root']['disallowedChildren']; - $this->assertNotContains('SilverStripe\\CMS\\Model\\ErrorPage', $classes); - $this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes); - $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); + Subsite::changeSubsite($s2); + $hints = Convert::json2array($cmsmain->SiteTreeHints()); + $classes = $hints['Root']['disallowedChildren']; + $this->assertNotContains(ErrorPage::class, $classes); + $this->assertNotContains('SiteTreeSubsitesTest_ClassA', $classes); + $this->assertNotContains('SiteTreeSubsitesTest_ClassB', $classes); + } + /** + * Tests that url segments between subsites don't conflict, but do conflict within them + */ + public function testValidateURLSegment() { + $this->logInWithPermission('ADMIN'); + // Saving existing page in the same subsite doesn't change urls + $mainHome = $this->objFromFixture('Page', 'home'); + $mainSubsiteID = $this->idFromFixture(Subsite::class, 'main'); + Subsite::changeSubsite($mainSubsiteID); + $mainHome->Content = '

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;