Completed
Push — master ( 5cf2d8...46bcff )
by Damian
13s
created

SiteTreeSubsites::augmentSQL()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 4.909
c 0
b 0
f 0
cc 9
eloc 20
nc 11
nop 2
1
<?php
2
3
namespace SilverStripe\Subsites\Extensions;
4
5
use Page;
6
use SilverStripe\CMS\Model\SiteTree;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Control\Director;
9
use SilverStripe\Control\HTTP;
10
use SilverStripe\Core\Config\Config;
11
use SilverStripe\Core\Convert;
12
use SilverStripe\Forms\CheckboxField;
13
use SilverStripe\Forms\DropdownField;
14
use SilverStripe\Forms\FieldList;
15
use SilverStripe\Forms\FormAction;
16
use SilverStripe\Forms\ToggleCompositeField;
17
use SilverStripe\ORM\DataExtension;
18
use SilverStripe\ORM\DataObject;
19
use SilverStripe\ORM\DataQuery;
20
use SilverStripe\ORM\Queries\SQLSelect;
21
use SilverStripe\Security\Member;
22
use SilverStripe\SiteConfig\SiteConfig;
23
use SilverStripe\Subsites\Model\Subsite;
24
use SilverStripe\Subsites\State\SubsiteState;
25
use SilverStripe\View\SSViewer;
26
27
/**
28
 * Extension for the SiteTree object to add subsites support
29
 */
30
class SiteTreeSubsites extends DataExtension
31
{
32
    private static $has_one = [
0 ignored issues
show
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
33
        'Subsite' => Subsite::class, // The subsite that this page belongs to
34
    ];
35
36
    private static $many_many = [
0 ignored issues
show
Unused Code introduced by
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
37
        'CrossSubsiteLinkTracking' => SiteTree::class // Stored separately, as the logic for URL rewriting is different
38
    ];
39
40
    private static $many_many_extraFields = [
0 ignored issues
show
Unused Code introduced by
The property $many_many_extraFields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
41
        'CrossSubsiteLinkTracking' => ['FieldName' => 'Varchar']
42
    ];
43
44
    public function isMainSite()
45
    {
46
        return $this->owner->SubsiteID == 0;
47
    }
48
49
    /**
50
     * Update any requests to limit the results to the current site
51
     * @param SQLSelect $query
52
     * @param DataQuery $dataQuery
53
     */
54
    public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
55
    {
56
        if (Subsite::$disable_subsite_filter) {
57
            return;
58
        }
59
        if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) {
60
            return;
61
        }
62
63
        // If you're querying by ID, ignore the sub-site - this is a bit ugly...
64
        // 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)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
65
        if ($query->filtersOnID()) {
66
            return;
67
        }
68
69
        $subsiteID = null;
0 ignored issues
show
Unused Code introduced by
$subsiteID is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
70
        if (Subsite::$force_subsite) {
71
            $subsiteID = Subsite::$force_subsite;
72
        } else {
73
            $subsiteID = SubsiteState::singleton()->getSubsiteId();
74
        }
75
76
        if ($subsiteID === null) {
77
            return;
78
        }
79
80
        // The foreach is an ugly way of getting the first key :-)
81
        foreach ($query->getFrom() as $tableName => $info) {
82
            // The tableName should be SiteTree or SiteTree_Live...
83
            $siteTreeTableName = SiteTree::getSchema()->tableName(SiteTree::class);
84
            if (strpos($tableName, $siteTreeTableName) === false) {
85
                break;
86
            }
87
            $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
88
            break;
89
        }
90
    }
91
92
    public function onBeforeWrite()
93
    {
94 View Code Duplication
        if (!$this->owner->ID && !$this->owner->SubsiteID) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
95
            $this->owner->SubsiteID = SubsiteState::singleton()->getSubsiteId();
96
        }
97
98
        parent::onBeforeWrite();
99
    }
100
101
    public function updateCMSFields(FieldList $fields)
102
    {
103
        $subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain');
104
        $subsitesMap = [];
105
        if ($subsites && $subsites->count()) {
106
            $subsitesToMap = $subsites->exclude('ID', $this->owner->SubsiteID);
107
            $subsitesMap = $subsitesToMap->map('ID', 'Title');
108
        }
109
110
        // Master page edit field (only allowed from default subsite to avoid inconsistent relationships)
111
        $isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite;
112
113
        if ($isDefaultSubsite && $subsitesMap) {
114
            $fields->addFieldToTab(
115
                'Root.Main',
116
                ToggleCompositeField::create(
117
                    'SubsiteOperations',
118
                    _t('SiteTreeSubsites.SubsiteOperations', 'Subsite Operations'),
119
                    [
120
                        new DropdownField('CopyToSubsiteID', _t(
121
                            'SiteTreeSubsites.CopyToSubsite',
122
                            'Copy page to subsite'
123
                        ), $subsitesMap),
124
                        new CheckboxField(
125
                            'CopyToSubsiteWithChildren',
126
                            _t('SiteTreeSubsites.CopyToSubsiteWithChildren', 'Include children pages?')
127
                        ),
128
                        $copyAction = new FormAction(
129
                            'copytosubsite',
130
                            _t('SiteTreeSubsites.CopyAction', 'Copy')
131
                        )
132
                    ]
133
                )->setHeadingLevel(4)
134
            );
135
136
            // @todo check if this needs re-implementation
137
//            $copyAction->includeDefaultJS(false);
138
        }
139
140
        // replace readonly link prefix
141
        $subsite = $this->owner->Subsite();
142
        $nested_urls_enabled = Config::inst()->get(SiteTree::class, 'nested_urls');
143
        if ($subsite && $subsite->exists()) {
144
            // Use baseurl from domain
145
            $baseLink = $subsite->absoluteBaseURL();
146
147
            // Add parent page if enabled
148
            if ($nested_urls_enabled && $this->owner->ParentID) {
149
                $baseLink = Controller::join_links(
150
                    $baseLink,
151
                    $this->owner->Parent()->RelativeLink(true)
152
                );
153
            }
154
155
            $urlsegment = $fields->dataFieldByName('URLSegment');
156
            $urlsegment->setURLPrefix($baseLink);
157
        }
158
    }
159
160
    /**
161
     * Does the basic duplication, but doesn't write anything
162
     * this means we can subclass this easier and do more complex
163
     * relation duplication.
164
     */
165
    public function duplicateToSubsitePrep($subsiteID)
166
    {
167
        if (is_object($subsiteID)) {
168
            $subsiteID = $subsiteID->ID;
169
        }
170
171
        $oldSubsite = SubsiteState::singleton()->getSubsiteId();
172
        if ($subsiteID) {
173
            Subsite::changeSubsite($subsiteID);
174
        } else {
175
            $subsiteID = $oldSubsite;
176
        }
177
        // doesn't write as we need to reset the SubsiteID, ParentID etc
178
        $clone = $this->owner->duplicate(false);
179
        $clone->CheckedPublicationDifferences = $clone->AddedToStage = true;
180
        $subsiteID = ($subsiteID ? $subsiteID : $oldSubsite);
181
        $clone->SubsiteID = $subsiteID;
182
        // We have no idea what the parentID should be, so as a workaround use the url-segment and subsite ID
183
        if ($this->owner->Parent()) {
184
            $parentSeg = $this->owner->Parent()->URLSegment;
185
            $newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first();
186
            if ($newParentPage) {
187
                $clone->ParentID = $newParentPage->ID;
188
            } else {
189
                // reset it to the top level, so the user can decide where to put it
190
                $clone->ParentID = 0;
191
            }
192
        }
193
        // MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module
194
        $clone->MasterPageID = $this->owner->ID;
195
        return $clone;
196
    }
197
198
    /**
199
     * Create a duplicate of this page and save it to another subsite
200
     * @param $subsiteID int|Subsite The Subsite to copy to, or its ID
201
     */
202
    public function duplicateToSubsite($subsiteID = null)
203
    {
204
        $clone = $this->owner->duplicateToSubsitePrep($subsiteID);
205
        $clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner);
206
        $clone->write();
207
        $clone->duplicateSubsiteRelations($this->owner);
208
        // new extension hooks which happens after write,
209
        // onAfterDuplicate isn't reliable due to
210
        // https://github.com/silverstripe/silverstripe-cms/issues/1253
211
        $clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner);
212
        return $clone;
213
    }
214
215
    /**
216
     * Duplicate relations using a static property to define
217
     * which ones we want to duplicate
218
     *
219
     * It may be that some relations are not diostinct to sub site so can stay
220
     * whereas others may need to be duplicated
221
     *
222
     */
223
    public function duplicateSubsiteRelations($originalPage)
224
    {
225
        $thisClass = $originalPage->ClassName;
226
        $relations = Config::inst()->get($thisClass, 'duplicate_to_subsite_relations');
227
228
        if ($relations && !empty($relations)) {
229
            foreach ($relations as $relation) {
230
                $items = $originalPage->$relation();
231
                foreach ($items as $item) {
232
                    $duplicateItem = $item->duplicate(false);
233
                    $duplicateItem->{$thisClass.'ID'} = $this->owner->ID;
234
                    $duplicateItem->write();
235
                }
236
            }
237
        }
238
    }
239
240
    /**
241
     * @return SiteConfig
242
     */
243
    public function alternateSiteConfig()
244
    {
245
        if (!$this->owner->SubsiteID) {
246
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by SilverStripe\Subsites\Ex...es::alternateSiteConfig of type SilverStripe\SiteConfig\SiteConfig.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
247
        }
248
        $sc = DataObject::get_one(SiteConfig::class, '"SubsiteID" = ' . $this->owner->SubsiteID);
249
        if (!$sc) {
250
            $sc = new SiteConfig();
251
            $sc->SubsiteID = $this->owner->SubsiteID;
252
            $sc->Title = _t('Subsite.SiteConfigTitle', 'Your Site Name');
253
            $sc->Tagline = _t('Subsite.SiteConfigSubtitle', 'Your tagline here');
254
            $sc->write();
255
        }
256
        return $sc;
257
    }
258
259
    /**
260
     * Only allow editing of a page if the member satisfies one of the following conditions:
261
     * - Is in a group which has access to the subsite this page belongs to
262
     * - Is in a group with edit permissions on the "main site"
263
     *
264
     * @param null $member
265
     * @return bool
266
     */
267
    public function canEdit($member = null)
268
    {
269
        if (!$member) {
270
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\Security\Member::currentUser() has been deprecated with message: 5.0.0 use Security::getCurrentUser()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
271
        }
272
273
        // Find the sites that this user has access to
274
        $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true, 'all', $member)->column('ID');
275
276
        if (!is_null($this->owner->SubsiteID)) {
277
            $subsiteID = $this->owner->SubsiteID;
278
        } else {
279
            // The relationships might not be available during the record creation when using a GridField.
280
            // In this case the related objects will have empty fields, and SubsiteID will not be available.
281
            //
282
            // We do the second best: fetch the likely SubsiteID from the session. The drawback is this might
283
            // make it possible to force relations to point to other (forbidden) subsites.
284
            $subsiteID = SubsiteState::singleton()->getSubsiteId();
285
        }
286
287
        // Return true if they have access to this object's site
288
        if (!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) {
289
            return false;
290
        }
291
    }
292
293
    /**
294
     * @param null $member
295
     * @return bool
296
     */
297 View Code Duplication
    public function canDelete($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
    {
299
        if (!$member && $member !== false) {
300
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\Security\Member::currentUser() has been deprecated with message: 5.0.0 use Security::getCurrentUser()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
301
        }
302
303
        return $this->canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \SilverStripe\Security\Member::currentUser() on line 300 can also be of type object<SilverStripe\Security\Member>; however, SilverStripe\Subsites\Ex...TreeSubsites::canEdit() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
304
    }
305
306
    /**
307
     * @param null $member
308
     * @return bool
309
     */
310 View Code Duplication
    public function canAddChildren($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
311
    {
312
        if (!$member && $member !== false) {
313
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\Security\Member::currentUser() has been deprecated with message: 5.0.0 use Security::getCurrentUser()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
314
        }
315
316
        return $this->canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \SilverStripe\Security\Member::currentUser() on line 313 can also be of type object<SilverStripe\Security\Member>; however, SilverStripe\Subsites\Ex...TreeSubsites::canEdit() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
317
    }
318
319
    /**
320
     * @param null $member
321
     * @return bool
322
     */
323 View Code Duplication
    public function canPublish($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324
    {
325
        if (!$member && $member !== false) {
326
            $member = Member::currentUser();
0 ignored issues
show
Deprecated Code introduced by
The method SilverStripe\Security\Member::currentUser() has been deprecated with message: 5.0.0 use Security::getCurrentUser()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
327
        }
328
329
        return $this->canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \SilverStripe\Security\Member::currentUser() on line 326 can also be of type object<SilverStripe\Security\Member>; however, SilverStripe\Subsites\Ex...TreeSubsites::canEdit() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
330
    }
331
332
    /**
333
     * Called by ContentController::init();
334
     * @param $controller
335
     */
336 View Code Duplication
    public static function contentcontrollerInit($controller)
0 ignored issues
show
Unused Code introduced by
The parameter $controller is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
337
    {
338
        $subsite = Subsite::currentSubsite();
339
340
        if ($subsite && $subsite->Theme) {
341
            SSViewer::set_themes(array_merge([$subsite->Theme], SSViewer::get_themes()));
342
        }
343
    }
344
345
    public function alternateAbsoluteLink()
346
    {
347
        // Generate the existing absolute URL and replace the domain with the subsite domain.
348
        // This helps deal with Link() returning an absolute URL.
349
        $url = Director::absoluteURL($this->owner->Link());
350
        if ($this->owner->SubsiteID) {
351
            $url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url);
352
        }
353
        return $url;
354
    }
355
356
    /**
357
     * Use the CMS domain for iframed CMS previews to prevent single-origin violations
358
     * and SSL cert problems.
359
     * @param null $action
360
     * @return string
361
     */
362
    public function alternatePreviewLink($action = null)
0 ignored issues
show
Unused Code introduced by
The parameter $action is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
363
    {
364
        $url = Director::absoluteURL($this->owner->Link());
365
        if ($this->owner->SubsiteID) {
366
            $url = HTTP::setGetVar('SubsiteID', $this->owner->SubsiteID, $url);
0 ignored issues
show
Security Bug introduced by
It seems like $url can also be of type false; however, SilverStripe\Control\HTTP::setGetVar() does only seem to accept string|null, did you maybe forget to handle an error condition?
Loading history...
367
        }
368
        return $url;
369
    }
370
371
    /**
372
     * Inject the subsite ID into the content so it can be used by frontend scripts.
373
     * @param $tags
374
     * @return string
375
     */
376
    public function MetaTags(&$tags)
377
    {
378
        if ($this->owner->SubsiteID) {
379
            $tags .= '<meta name="x-subsite-id" content="' . $this->owner->SubsiteID . "\" />\n";
380
        }
381
382
        return $tags;
383
    }
384
385
    public function augmentSyncLinkTracking()
386
    {
387
        // Set LinkTracking appropriately
388
        $links = HTTP::getLinksIn($this->owner->Content);
389
        $linkedPages = [];
390
391
        if ($links) {
392
            foreach ($links as $link) {
393
                if (substr($link, 0, strlen('http://')) == 'http://') {
394
                    $withoutHttp = substr($link, strlen('http://'));
395
                    if (strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) {
396
                        $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/'));
397
                        $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1);
398
399
                        $subsiteID = Subsite::getSubsiteIDForDomain($domain);
400
                        if ($subsiteID == 0) {
401
                            continue;
402
                        } // We have no idea what the domain for the main site is, so cant track links to it
403
404
                        $origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
405
                        Subsite::disable_subsite_filter(true);
406
                        $candidatePage = DataObject::get_one(
407
                            SiteTree::class,
408
                            "\"URLSegment\" = '" . Convert::raw2sql(urldecode($rest)) . "' AND \"SubsiteID\" = " . $subsiteID,
409
                            false
410
                        );
411
                        Subsite::disable_subsite_filter($origDisableSubsiteFilter);
412
413
                        if ($candidatePage) {
414
                            $linkedPages[] = $candidatePage->ID;
415
                        } else {
416
                            $this->owner->HasBrokenLink = true;
417
                        }
418
                    }
419
                }
420
            }
421
        }
422
423
        $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
424
    }
425
426
    /**
427
     * Ensure that valid url segments are checked within the correct subsite of the owner object,
428
     * even if the current subsiteID is set to some other subsite.
429
     *
430
     * @return null|bool Either true or false, or null to not influence result
431
     */
432
    public function augmentValidURLSegment()
433
    {
434
        // If this page is being filtered in the current subsite, then no custom validation query is required.
435
        $subsite = Subsite::$force_subsite ?: SubsiteState::singleton()->getSubsiteId();
436
        if (empty($this->owner->SubsiteID) || $subsite == $this->owner->SubsiteID) {
437
            return null;
438
        }
439
440
        // Backup forced subsite
441
        $prevForceSubsite = Subsite::$force_subsite;
442
        Subsite::$force_subsite = $this->owner->SubsiteID;
443
444
        // Repeat validation in the correct subsite
445
        $isValid = $this->owner->validURLSegment();
446
447
        // Restore
448
        Subsite::$force_subsite = $prevForceSubsite;
449
450
        return (bool)$isValid;
451
    }
452
453
    /**
454
     * Return a piece of text to keep DataObject cache keys appropriately specific
455
     */
456
    public function cacheKeyComponent()
457
    {
458
        return 'subsite-' . SubsiteState::singleton()->getSubsiteId();
459
    }
460
461
    /**
462
     * @param Member
463
     * @return boolean|null
464
     */
465
    public function canCreate($member = null)
466
    {
467
        // Typically called on a singleton, so we're not using the Subsite() relation
468
        $subsite = Subsite::currentSubsite();
469
        if ($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) {
470
            $blacklisted = explode(',', $subsite->PageTypeBlacklist);
471
            // All subclasses need to be listed explicitly
472
            if (in_array($this->owner->class, $blacklisted)) {
473
                return false;
474
            }
475
        }
476
    }
477
}
478