diff --git a/README.md b/README.md index ebdd63d..9c211f6 100644 --- a/README.md +++ b/README.md @@ -22,52 +22,6 @@ The subsites module allows multiple websites to run from a single installation o ## Usage -### Limit allowed domains - -You can list available domains for your subsites (Example: subdomain.''domain''.tld). The subsites are generally identified only by their subdomain part (Example: ''subdomain''.domain.tld). - -This example would let you create subsites such as ''wellington.mycompany.com'' or ''london.mycompany.org'' - -*mysite/_config.php* - - :::php - Subsite::set_allowed_domains(array( - 'mycompany.com', - 'mycompany.org' - )); - - -If you would like to be able to choose any domain for your subsite, rather than subdomains off a common base, then list top-level domains in your `set_allowed_domains()` list. - -In this example, your subsite name (e.g. ''silverstripe''), will be appended to a much shorter base domain (e.g. ''co.nz'', or ''org''). This would let you create subsites with domains such as ''silverstripe.org'' or ''example.co.nz'' - -*mysite/_config.php* - - :::php - Subsite::set_allowed_domains(array( - 'com', - 'org', - 'co.nz', - 'org.nz', - )); - - -You can mix the two together, if you want to have some subsites hosted off subdomains of your mail site, and some subsites hosted off their own domains. In this example, you could set up subsites at ''wellington.example.com'', ''othersite.co.nz'', or ''thirdsite.org''. - -*mysite/_config.php* - - :::php - Subsite::set_allowed_domains(array( - 'example.com', - 'com', - 'org', - 'co.nz', - 'org.nz', - )); - - -Note that every site also has a ''www.''-prefixed version of the domain available. For example, if your subsite is accessible from ''wellington.example.org'' then it will also be accessible from '''www.wellington.example.org''. - ### Strict Subdomain Matching ### The module tries to provide sensible defaults, in which it regards `example.com` and `www.example.com` as the same domains. In case you want to distinguish between these variations, set `Subsite::$strict_subdomain_matching` to TRUE. This won't affect wildcard/asterisk checks, but removes the ambiguity about default subdomains. diff --git a/code/model/Subsite.php b/code/model/Subsite.php index 7eb1e74..751e94a 100644 --- a/code/model/Subsite.php +++ b/code/model/Subsite.php @@ -20,59 +20,32 @@ class Subsite extends DataObject implements PermissionProvider { * @var boolean $disable_subsite_filter If enabled, bypasses the query decoration * to limit DataObject::get*() calls to a specific subsite. Useful for debugging. */ - static $disable_subsite_filter = false; + public static $disable_subsite_filter = false; /** * Allows you to force a specific subsite ID, or comma separated list of IDs. * Only works for reading. An object cannot be written to more than 1 subsite. */ - static $force_subsite = null; - - static $write_hostmap = true; - - private static $default_sort = "\"Title\" ASC"; - - private static $db = array( - 'Title' => 'Varchar(255)', - 'RedirectURL' => 'Varchar(255)', - 'DefaultSite' => 'Boolean', - 'Theme' => 'Varchar', - 'Language' => 'Varchar(6)', - - // Used to hide unfinished/private subsites from public view. - // If unset, will default to true - 'IsPublic' => 'Boolean', - - // Comma-separated list of disallowed page types - 'PageTypeBlacklist' => 'Text', - ); - - private static $has_one = array( - ); - - private static $has_many = array( - 'Domains' => 'SubsiteDomain', - ); - - private static $belongs_many_many = array( - "Groups" => "Group", - ); - - private static $defaults = array( - 'IsPublic' => 1 - ); - - private static $searchable_fields = array( - 'Title', - 'Domains.Domain', - 'IsPublic', - ); + public static $force_subsite = null; + /** + * + * @var boolean + */ + public static $write_hostmap = true; + /** * Memory cache of accessible sites + * + * @array */ private static $_cache_accessible_sites = array(); + /** + * Memory cache of subsite id for domains + * + * @var array + */ private static $_cache_subsite_for_domain = array(); /** @@ -87,244 +60,29 @@ class Subsite extends DataObject implements PermissionProvider { * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com') * in both TRUE or FALSE setting. */ - static $strict_subdomain_matching = false; + public static $strict_subdomain_matching = false; /** * @var boolean Respects the IsPublic flag when retrieving subsites */ - static $check_is_public = true; + public static $check_is_public = true; - static function set_allowed_domains($domain){ - user_error('Subsite::set_allowed_domains() is deprecated; it is no longer necessary ' - . 'because users can now enter any domain name', E_USER_NOTICE); - } - - static function set_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; } - - /** - * Return the themes that can be used with this subsite, as an array of themecode => description - */ - function allowedThemes() { - if($themes = $this->stat('allowed_themes')) { - return ArrayLib::valuekey($themes); - } else { - $themes = array(); - if(is_dir('../themes/')) { - foreach(scandir('../themes/') as $theme) { - if($theme[0] == '.') continue; - $theme = strtok($theme,'_'); - $themes[$theme] = $theme; - } - ksort($themes); - } - return $themes; - } - } - - public function getLanguage() { - if($this->getField('Language')) { - return $this->getField('Language'); - } else { - return i18n::get_locale(); - } - } - - public function validate() { - $result = parent::validate(); - if(!$this->Title) { - $result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"')); - } - - return $result; - } - - /** - * Whenever a Subsite is written, rewrite the hostmap - * - * @return void - */ - public function onAfterWrite() { - Subsite::writeHostMap(); - parent::onAfterWrite(); - } - /** - * Return the primary domain of this site. Tries to "normalize" the domain name, - * by replacing potential wildcards. - * - * @return string The full domain name of this subsite (without protocol prefix) - */ - function domain() { - if($this->ID) { - $domains = DataObject::get("SubsiteDomain", "\"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']; - } - } - - function getPrimaryDomain() { - return $this->domain(); - } - - function absoluteBaseURL() { - return "http://" . $this->domain() . Director::baseURL(); - } - - /** - * Show the configuration fields for each subsite - */ - function getCMSFields() { - if($this->ID!=0) { - $domainTable = new GridField( - "Domains", - _t('Subsite.DomainsListTitle',"Domains"), - $this->Domains(), - GridFieldConfig_RecordEditor::create(10) - ); - }else { - $domainTable = new LiteralField( - 'Domains', - '
'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'
' - ); - } - - $languageSelector = new DropdownField( - 'Language', - $this->fieldLabel('Language'), - i18n::get_common_locales() - ); - - $pageTypeMap = array(); - $pageTypes = SiteTree::page_type_classes(); - foreach($pageTypes as $pageType) { - $pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name(); - } - asort($pageTypeMap); - - $fields = new FieldList( - $subsiteTabs = new TabSet('Root', - new Tab( - 'Configuration', - _t('Subsite.TabTitleConfig', 'Configuration'), - new HeaderField($this->getClassName() . ' configuration', 2), - new TextField('Title', $this->fieldLabel('Title'), $this->Title), - - new HeaderField( - _t('Subsite.DomainsHeadline',"Domains for this subsite") - ), - $domainTable, - $languageSelector, - // new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL), - new CheckboxField('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite), - new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic), - - new DropdownField('Theme',$this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme), - - - new LiteralField( - 'PageTypeBlacklistToggle', - sprintf( - '', - _t('Subsite.PageTypeBlacklistField', 'Disallow page types?') - ) - ), - new CheckboxSetField( - 'PageTypeBlacklist', - false, - $pageTypeMap - ) - ) - ), - new HiddenField('ID', '', $this->ID), - new HiddenField('IsSubsite', '', 1) - ); - - $subsiteTabs->addExtraClass('subsite-model'); - - $this->extend('updateCMSFields', $fields); - return $fields; - } - - 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; - } - - public function summaryFields() { - return array( - 'Title' => $this->fieldLabel('Title'), - 'PrimaryDomain' => $this->fieldLabel('PrimaryDomain'), - 'IsPublic' => _t('Subsite.IsPublicHeaderField','Active subsite'), - ); - } - - /** - * @todo getClassName is redundant, already stored as a database field? - */ - function getClassName() { - return $this->class; - } - - function getCMSActions() { - return new FieldList( - new FormAction( - 'callPageMethod', - _t('Subsite.ButtonLabelCopy',"Create copy"), - null, - 'adminDuplicate' - ) - ); - } - - function adminDuplicate() { - $newItem = $this->duplicate(); - $message = _t( - 'Subsite.CopyMessage', - 'Created a copy of {title}', - array('title' => Convert::raw2js($this->Title)) - ); - - return <<'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'
' + ); + } + + $languageSelector = new DropdownField( + 'Language', + $this->fieldLabel('Language'), + i18n::get_common_locales() + ); + + $pageTypeMap = array(); + $pageTypes = SiteTree::page_type_classes(); + foreach($pageTypes as $pageType) { + $pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name(); + } + asort($pageTypeMap); - 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); + $fields = new FieldList( + $subsiteTabs = new TabSet('Root', + new Tab( + 'Configuration', + _t('Subsite.TabTitleConfig', 'Configuration'), + new HeaderField($this->getClassName() . ' configuration', 2), + new TextField('Title', $this->fieldLabel('Title'), $this->Title), + + new HeaderField( + _t('Subsite.DomainsHeadline',"Domains for this subsite") + ), + $domainTable, + $languageSelector, + // new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL), + new CheckboxField('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite), + new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic), + + new DropdownField('Theme',$this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme), + + + new LiteralField( + 'PageTypeBlacklistToggle', + sprintf( + '', + _t('Subsite.PageTypeBlacklistField', 'Disallow page types?') + ) + ), + new CheckboxSetField( + 'PageTypeBlacklist', + false, + $pageTypeMap + ) + ) + ), + new HiddenField('ID', '', $this->ID), + new HiddenField('IsSubsite', '', 1) + ); + + $subsiteTabs->addExtraClass('subsite-model'); + + $this->extend('updateCMSFields', $fields); + return $fields; + } + + /** + * + * @param boolean $includerelations + * @return array + */ + public function fieldLabels($includerelations = true) { + $labels = parent::fieldLabels($includerelations); + $labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name'); + $labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL'); + $labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site'); + $labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme'); + $labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language'); + $labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access'); + $labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist'); + $labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain'); + $labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain'); + + return $labels; + } + + /** + * + * @return array + */ + public function summaryFields() { + return array( + 'Title' => $this->fieldLabel('Title'), + 'PrimaryDomain' => $this->fieldLabel('PrimaryDomain'), + 'IsPublic' => _t('Subsite.IsPublicHeaderField','Active subsite'), + ); + } + + /** + * Return the themes that can be used with this subsite, as an array of themecode => description + * + * @return array + */ + public function allowedThemes() { + if($themes = $this->stat('allowed_themes')) { + return ArrayLib::valuekey($themes); + } else { + $themes = array(); + if(is_dir('../themes/')) { + foreach(scandir('../themes/') as $theme) { + if($theme[0] == '.') continue; + $theme = strtok($theme,'_'); + $themes[$theme] = $theme; + } + ksort($themes); + } + return $themes; + } + } + + /** + * @return string Current locale of the subsite + */ + public function getLanguage() { + if($this->getField('Language')) { + return $this->getField('Language'); + } else { + return i18n::get_locale(); + } + } + + /** + * + * @return ValidationResult + */ + public function validate() { + $result = parent::validate(); + if(!$this->Title) { + $result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"')); + } return $result; } /** - * Disable the sub-site filtering; queries will select from all subsites + * Whenever a Subsite is written, rewrite the hostmap + * + * @return void */ - static function disable_subsite_filter($disabled = true) { - self::$disable_subsite_filter = $disabled; + public function onAfterWrite() { + Subsite::writeHostMap(); + parent::onAfterWrite(); } /** - * Flush caches on database reset + * Return the primary domain of this site. Tries to "normalize" the domain name, + * by replacing potential wildcards. + * + * @return string The full domain name of this subsite (without protocol prefix) */ - static function on_db_reset() { - self::$_cache_accessible_sites = array(); - self::$_cache_subsite_for_domain = array(); + public function domain() { + if($this->ID) { + $domains = DataObject::get("SubsiteDomain", "\"SubsiteID\" = $this->ID", "\"IsPrimary\" DESC","", 1); + if($domains && $domains->Count()>0) { + $domain = $domains->First()->Domain; + // If there are wildcards in the primary domain (not recommended), make some + // educated guesses about what to replace them with: + $domain = preg_replace('/\.\*$/',".$_SERVER[HTTP_HOST]", $domain); + // Default to "subsite." prefix for first wildcard + // TODO Whats the significance of "subsite" in this context?! + $domain = preg_replace('/^\*\./',"subsite.", $domain); + // *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com' + $domain = str_replace('.www.','.', $domain); + + return $domain; + } + + // SubsiteID = 0 is often used to refer to the main site, just return $_SERVER['HTTP_HOST'] + } else { + return $_SERVER['HTTP_HOST']; + } + } + + /** + * + * @return string - The full domain name of this subsite (without protocol prefix) + */ + public function getPrimaryDomain() { + return $this->domain(); + } + + /** + * + * @return string + */ + public function absoluteBaseURL() { + return "http://" . $this->domain() . Director::baseURL(); + } + + /** + * @todo getClassName is redundant, already stored as a database field? + */ + public function getClassName() { + return $this->class; + } + + /** + * Javascript admin action to duplicate this subsite + * + * @return string - javascript + */ + public function adminDuplicate() { + $newItem = $this->duplicate(); + $message = _t( + 'Subsite.CopyMessage', + 'Created a copy of {title}', + array('title' => Convert::raw2js($this->Title)) + ); + + return <<