Version: current

      1 <?php
      2
      3 /**
      4  * Extension for the SiteTree object to add subsites support
      5  */
      6 class SiteTreeSubsites extends SiteTreeDecorator {
      7     static $template_variables = array(
      8         '((Company Name))' => 'Title'
      9     );
     10
     11     static $template_fields = array(
     12         "URLSegment",
     13         "Title",
     14         "MenuTitle",
     15         "Content",
     16         "MetaTitle",
     17         "MetaDescription",
     18         "MetaKeywords",
     19     );
     20
     21     /**
     22      * Set the fields that will be copied from the template.
     23      * Note that ParentID and Sort are implied.
     24      */
     25     static function set_template_fields($fieldList) {
     26         self::$template_fields = $fieldList;
     27     }
     28
     29         static function get_homepage_urlsegment_by_subsite($SubsiteID){
     30             $homepage = DataObject::get_one("SiteTree", "\"SiteTree\".\"URLSegment\"='". RootURLController::get_default_homepage_link() ."' AND \"SiteTree\".\"SubsiteID\" = " . $SubsiteID);
     31             if (!$homepage) $homepage = DataObject::get_one("SiteTree", "\"SiteTree\".\"ClassName\" LIKE '%HomePage%' AND \"SiteTree\".\"SubsiteID\" = " . $SubsiteID);
     32             return ($homepage) ? $homepage->URLSegment : false;
     33         }
     34
     35
     36     function extraStatics() {
     37         if(!method_exists('DataObjectDecorator', 'load_extra_statics') && $this->owner->class != 'SiteTree') return null;
     38         return array(
     39             'has_one' => array(
     40                 'Subsite' => 'Subsite', // The subsite that this page belongs to
     41                 'MasterPage' => 'SiteTree',// Optional; the page that is the content master
     42             ),
     43             'has_many' => array(
     44                 'RelatedPages' => 'RelatedPageLink'
     45             ),
     46             'many_many' => array(
     47                 'CrossSubsiteLinkTracking' => 'SiteTree' // Stored separately, as the logic for URL rewriting is different
     48             ),
     49             'belongs_many_many' => array(
     50                 'BackCrossSubsiteLinkTracking' => 'SiteTree'
     51             )
     52         );
     53     }
     54
     55     function isMainSite() {
     56         if($this->owner->SubsiteID == 0) return true;
     57         return false;
     58     }
     59
     60     /**
     61      * Update any requests to limit the results to the current site
     62      */
     63     function augmentSQL(SQLQuery &$query) {
     64         if(Subsite::$disable_subsite_filter) return;
     65
     66         // Don't run on delete queries, since they are always tied to
     67         // a specific ID.
     68         if ($query->delete) return;
     69
     70         // If you're querying by ID, ignore the sub-site - this is a bit ugly...
     71         // if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) {
     72         if (!$query->where || (!preg_match('/\.(\'|"|`|)ID(\'|"|`|)( ?)=/', $query->where[0]))) {
     73
     74             if (Subsite::$force_subsite) $subsiteID = Subsite::$force_subsite;
     75             else {
     76                 if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
     77                 else $subsiteID = (int)Subsite::currentSubsiteID();
     78             }
     79
     80             // The foreach is an ugly way of getting the first key :-)
     81             foreach($query->from as $tableName => $info) {
     82                 // The tableName should be SiteTree or SiteTree_Live...
     83                 if(strpos($tableName,'SiteTree') === false) break;
     84                 $query->where[] = "\"$tableName\".\"SubsiteID\" IN ($subsiteID)";
     85                 break;
     86             }
     87         }
     88     }
     89
     90     /**
     91      * Call this method before writing; the next write carried out by the system won't
     92      * set the CustomContent value
     93      */
     94     function nextWriteDoesntCustomise() {
     95         $this->nextWriteDoesntCustomise = true;
     96     }
     97
     98     protected $nextWriteDoesntCustomise = false;
     99
    100     function onBeforeWrite() {
    101         if(!$this->owner->ID && !$this->owner->SubsiteID) $this->owner->SubsiteID = Subsite::currentSubsiteID();
    102
    103         // If the content has been changed, then the page should be marked as 'custom content'
    104         if(!$this->nextWriteDoesntCustomise && $this->owner->ID && $this->owner->MasterPageID && !$this->owner->CustomContent) {
    105             $changed = $this->owner->getChangedFields();
    106
    107             foreach(self::$template_fields as $field) {
    108                 if(isset($changed[$field]) && $changed[$field]) {
    109                     $this->owner->CustomContent = true;
    110                     FormResponse::add("if($('Form_EditForm_CustomContent')) $('Form_EditForm_CustomContent').checked = true;");
    111                     break;
    112                 }
    113             }
    114         }
    115
    116         $this->nextWriteDoesntCustomise = false;
    117
    118         parent::onBeforeWrite();
    119     }
    120
    121     function onAfterWrite(&$original) {
    122         // Update any subsite virtual pages that might need updating
    123         $oldState = Subsite::$disable_subsite_filter;
    124         Subsite::$disable_subsite_filter = true;
    125
    126         $linkedPages = DataObject::get("SubsitesVirtualPage", "\"CopyContentFromID\" = {$this->owner->ID}");
    127         if($linkedPages) foreach($linkedPages as $page) {
    128             $page->copyFrom($page->CopyContentFrom());
    129             $page->write();
    130         }
    131
    132         Subsite::$disable_subsite_filter = $oldState;
    133     }
    134
    135     function onAfterPublish(&$original) {
    136         // Publish any subsite virtual pages that might need publishing
    137         $oldState = Subsite::$disable_subsite_filter;
    138         Subsite::$disable_subsite_filter = true;
    139
    140         $linkedPages = DataObject::get("SubsitesVirtualPage", "\"CopyContentFromID\" = {$this->owner->ID}");
    141         if($linkedPages) foreach($linkedPages as $page) {
    142             $page->copyFrom($page->CopyContentFrom());
    143             if($page->ExistsOnLive) $page->doPublish();
    144         }
    145
    146         Subsite::$disable_subsite_filter = $oldState;
    147     }
    148
    149     function updateCMSFields(&$fields) {
    150         if($this->owner->MasterPageID) $fields->insertFirst(new HeaderField('This page\'s content is copied from a master page: ' . $this->owner->MasterPage()->Title, 2));
    151
    152         // replace readonly link prefix
    153         $subsite = $this->owner->Subsite();
    154         if($subsite && $subsite->ID) {
    155             $baseUrl = 'http://' . $subsite->domain() . '/';
    156             $fields->removeByName('BaseUrlLabel');
    157             $fields->addFieldToTab(
    158                 'Root.Content.Metadata',
    159                 new LabelField('BaseUrlLabel',$baseUrl),
    160                 'URLSegment'
    161             );
    162         }
    163
    164         $relatedCount = 0;
    165         $reverse = $this->ReverseRelated();
    166         if($reverse) $relatedCount += $reverse->Count();
    167         $normalRelated = $this->NormalRelated();
    168         if($normalRelated) $relatedCount += $normalRelated->Count();
    169
    170         $tabName = $relatedCount ? 'Related (' . $relatedCount . ')' : 'Related';
    171         $tab = $fields->findOrMakeTab('Root.Related', $tabName);
    172         // Related pages
    173         $tab->push(new LiteralField('RelatedNote', '<p>You can list pages here that are related to this page.<br />When this page is updated, you will get a reminder to check whether these related pages need to be updated as well.</p>'));
    174         $tab->push(
    175             $related = new ComplexTableField(
    176                     $this,
    177                     'RelatedPages',
    178                     'RelatedPageLink',
    179                     array(
    180                         'RelatedPageAdminLink' => 'Page',
    181                         'AbsoluteLink' => 'URL',
    182                     )
    183             )
    184         );
    185
    186         // The 'show' link doesn't provide any useful info
    187         $related->setPermissions(array('add', 'edit', 'delete'));
    188
    189         if($reverse) {
    190             $text = '<p>In addition, this page is marked as related by the following pages: </p><p>';
    191             foreach($reverse as $rpage) {
    192                 $text .= $rpage->RelatedPageAdminLink(true) . " - " . $rpage->AbsoluteLink(true) . "<br />\n";
    193             }
    194             $text .= '</p>';
    195
    196             $tab->push(new LiteralField('ReverseRelated', $text));
    197         }
    198
    199         $virtualPagesTable = new SubsiteAgnosticTableListField(
    200             'VirtualPageTracking',
    201             'SiteTree',
    202             array(
    203                 'Title' => 'Title',
    204                 'AbsoluteLink' => 'URL',
    205                 'Subsite.Title' => 'Subsite'
    206             ),
    207             '"CopyContentFromID" = ' . $this->owner->ID,
    208             ''
    209         );
    210         $virtualPagesTable->setFieldFormatting(array(
    211             'Title' => '<a href=\"admin/show/$ID\">$Title</a>'
    212         ));
    213         $virtualPagesTable->setPermissions(array(
    214             'show',
    215             'export'
    216         ));
    217
    218
    219
    220         if ($tab = $fields->fieldByName('Root.VirtualPages')) {
    221             $tab->removeByName('VirtualPageTracking');
    222             $tab->push($virtualPagesTable);
    223         } else {
    224             if ($virtualPagesTable->TotalCount()) {
    225                 $virtualPagesNote = new LiteralField('BackLinksNote', '<p>' . _t('SiteTree.VIRTUALPAGESLINKING', 'The following virtual pages pull from this page:') . '</p>');
    226                 $fields->fieldByName('Root')->push($tabVirtualPages = new Tab('VirtualPages',
    227                     $virtualPagesNote,
    228                     $virtualPagesTable
    229                 ));
    230             }
    231         }
    232
    233     }
    234
    235     /**
    236      * Returns the RelatedPageLink objects that are reverse-associated with this page.
    237      */
    238     function ReverseRelated() {
    239         return DataObject::get('RelatedPageLink', "\"RelatedPageLink\".\"RelatedPageID\" = {$this->owner->ID}
    240             AND R2.\"ID\" IS NULL", '',
    241             "INNER JOIN \"SiteTree\" ON \"SiteTree\".\"ID\" = \"RelatedPageLink\".\"MasterPageID\"
    242             LEFT JOIN \"RelatedPageLink\" AS R2 ON R2.\"MasterPageID\" = {$this->owner->ID}
    243             AND R2.\"RelatedPageID\" = \"RelatedPageLink\".\"MasterPageID\"
    244             "
    245         );
    246     }
    247
    248     function NormalRelated() {
    249         $return = new DataObjectSet();
    250         $links = DataObject::get('RelatedPageLink', '"MasterPageID" = ' . $this->owner->ID);
    251         if($links) foreach($links as $link) {
    252             if($link->RelatedPage()->exists()) {
    253                 $return->push($link->RelatedPage());
    254             }
    255         }
    256
    257         return $return->Count() > 0 ? $return : false;
    258     }
    259
    260     function alternateSiteConfig() {
    261         if(!$this->owner->SubsiteID) return false;
    262         $sc = DataObject::get_one('SiteConfig', '"SubsiteID" = ' . $this->owner->SubsiteID);
    263         if(!$sc) {
    264             $sc = new SiteConfig();
    265             $sc->SubsiteID = $this->owner->SubsiteID;
    266             $sc->Title = 'Your Site Name';
    267             $sc->Tagline = 'your tagline here';
    268             $sc->write();
    269         }
    270         return $sc;
    271     }
    272
    273     /**
    274      * Only allow editing of a page if the member satisfies one of the following conditions:
    275      * - Is in a group which has access to the subsite this page belongs to
    276      * - Is in a group with edit permissions on the "main site"
    277      *
    278      * @return boolean
    279      */
    280     function canEdit($member = null) {
    281         if(!$member) $member = Member::currentUser();
    282
    283         // Find the sites that this user has access to
    284         $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain',true,'all',$member)->column('ID');
    285
    286         // Return true if they have access to this object's site
    287         if(!(in_array(0, $goodSites) || in_array($this->owner->SubsiteID, $goodSites))) return false;
    288     }
    289
    290     /**
    291      * @return boolean
    292      */
    293     function canDelete($member = null) {
    294         if(!$member && $member !== FALSE) $member = Member::currentUser();
    295
    296         return $this->canEdit($member);
    297     }
    298
    299     /**
    300      * @return boolean
    301      */
    302     function canAddChildren($member = null) {
    303         if(!$member && $member !== FALSE) $member = Member::currentUser();
    304
    305         return $this->canEdit($member);
    306     }
    307
    308     /**
    309      * @return boolean
    310      */
    311     function canPublish($member = null) {
    312         if(!$member && $member !== FALSE) $member = Member::currentUser();
    313
    314         return $this->canEdit($member);
    315     }
    316
    317     /**
    318      * Create a duplicate of this page and save it to another subsite
    319      * @param $subsiteID int|Subsite The Subsite to copy to, or its ID
    320      * @param $isTemplate boolean If this is true, then the current page will be treated as the template, and MasterPageID will be set
    321      */
    322     public function duplicateToSubsite($subsiteID = null, $isTemplate = true) {
    323         if(is_object($subsiteID)) {
    324             $subsite = $subsiteID;
    325             $subsiteID = $subsite->ID;
    326         } else $subsite = DataObject::get_by_id('Subsite', $subsiteID);
    327
    328         $page = $this->owner->duplicate(false);
    329
    330         $page->CheckedPublicationDifferences = $page->AddedToStage = true;
    331         $subsiteID = ($subsiteID ? $subsiteID : Subsite::currentSubsiteID());
    332         $page->SubsiteID = $subsiteID;
    333
    334         if($isTemplate) $page->MasterPageID = $this->owner->ID;
    335
    336         $page->write();
    337
    338         return $page;
    339     }
    340
    341     /**
    342      * Called by ContentController::init();
    343      */
    344     static function contentcontrollerInit($controller) {
    345         // Need to set the SubsiteID to null incase we've been in the CMS
    346         Session::set('SubsiteID', null);
    347         $subsite = Subsite::currentSubsite();
    348         if ($subsite && $subsite->Language) {
    349             if (array_key_exists($subsite->Language, i18n::$common_locales)) {
    350                 i18n::set_locale($subsite->Language);
    351             }
    352             elseif (isset(i18n::$likely_subtags[$subsite->Language])) {
    353                 i18n::set_locale(i18n::$likely_subtags[$subsite->Language]);
    354             }
    355         }
    356         if($subsite && $subsite->Theme) SSViewer::set_theme(Subsite::currentSubsite()->Theme);
    357     }
    358
    359     /**
    360      * Called by ModelAsController::init();
    361      */
    362     static function modelascontrollerInit($controller) {
    363         // Need to set the SubsiteID to null incase we've been in the CMS
    364         Session::set('SubsiteID', null);
    365     }
    366
    367     function alternateAbsoluteLink() {
    368         // Generate the existing absolute URL and replace the domain with the subsite domain.
    369         // This helps deal with Link() returning an absolute URL.
    370         $url = Director::absoluteURL($this->owner->Link());
    371         if($this->owner->SubsiteID) {
    372                         $domain = $this->owner->Subsite()->domain();
    373                         if($domain[0]=='/') $domain = $_SERVER['HTTP_HOST'].$domain;
    374             $url = preg_replace('/\/\/[^\/]+\//', '//' .  $domain . '/', $url);
    375         }
    376         return $url;
    377     }
    378
    379     function augmentSyncLinkTracking() {
    380         // Set LinkTracking appropriately
    381         $links = HTTP::getLinksIn($this->owner->Content);
    382         $linkedPages = array();
    383
    384         if($links) foreach($links as $link) {
    385             if(substr($link, 0, strlen('http://')) == 'http://') {
    386                 $withoutHttp = substr($link, strlen('http://'));
    387                 if(strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) {
    388                     $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/'));
    389                     $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1);
    390
    391                     $subsiteID = Subsite::getSubsiteIDForDomain($domain);
    392                     if($subsiteID == 0) continue; // We have no idea what the domain for the main site is, so cant track links to it
    393
    394                     Subsite::disable_subsite_filter(true);
    395                     $candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . urldecode( $rest). "' AND \"SubsiteID\" = " . $subsiteID, false);
    396                     Subsite::disable_subsite_filter(false);
    397
    398                     if($candidatePage) {
    399                         $linkedPages[] = $candidatePage->ID;
    400                     } else {
    401                         $this->owner->HasBrokenLink = true;
    402                     }
    403                 }
    404             }
    405         }
    406
    407         $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
    408     }
    409
    410     /**
    411      * Return a piece of text to keep DataObject cache keys appropriately specific
    412      */
    413     function cacheKeyComponent() {
    414         return 'subsite-'.Subsite::currentSubsiteID();
    415     }
    416 }
    417
    418 ?>
    419

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -