Completed
Push — master ( 46bcff...793b46 )
by Robbie
01:45
created

SiteTreeSubsites::contentcontrollerInit()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 6
nc 4
nop 1
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\i18n\i18n;
18
use SilverStripe\ORM\DataExtension;
19
use SilverStripe\ORM\DataObject;
20
use SilverStripe\ORM\DataQuery;
21
use SilverStripe\ORM\Queries\SQLSelect;
22
use SilverStripe\Security\Member;
23
use SilverStripe\SiteConfig\SiteConfig;
24
use SilverStripe\Subsites\Model\Subsite;
25
use SilverStripe\Subsites\State\SubsiteState;
26
use SilverStripe\View\SSViewer;
27
28
/**
29
 * Extension for the SiteTree object to add subsites support
30
 */
31
class SiteTreeSubsites extends DataExtension
32
{
33
    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...
34
        'Subsite' => Subsite::class, // The subsite that this page belongs to
35
    ];
36
37
    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...
38
        'CrossSubsiteLinkTracking' => SiteTree::class // Stored separately, as the logic for URL rewriting is different
39
    ];
40
41
    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...
42
        'CrossSubsiteLinkTracking' => ['FieldName' => 'Varchar']
43
    ];
44
45
    public function isMainSite()
46
    {
47
        return $this->owner->SubsiteID == 0;
48
    }
49
50
    /**
51
     * Update any requests to limit the results to the current site
52
     * @param SQLSelect $query
53
     * @param DataQuery $dataQuery
54
     */
55
    public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
56
    {
57
        if (Subsite::$disable_subsite_filter) {
58
            return;
59
        }
60
        if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) {
61
            return;
62
        }
63
64
        // If you're querying by ID, ignore the sub-site - this is a bit ugly...
65
        // 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...
66
        if ($query->filtersOnID()) {
67
            return;
68
        }
69
70
        $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...
71
        if (Subsite::$force_subsite) {
72
            $subsiteID = Subsite::$force_subsite;
73
        } else {
74
            $subsiteID = SubsiteState::singleton()->getSubsiteId();
75
        }
76
77
        if ($subsiteID === null) {
78
            return;
79
        }
80
81
        // The foreach is an ugly way of getting the first key :-)
82
        foreach ($query->getFrom() as $tableName => $info) {
83
            // The tableName should be SiteTree or SiteTree_Live...
84
            $siteTreeTableName = SiteTree::getSchema()->tableName(SiteTree::class);
85
            if (strpos($tableName, $siteTreeTableName) === false) {
86
                break;
87
            }
88
            $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
89
            break;
90
        }
91
    }
92
93
    public function onBeforeWrite()
94
    {
95 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...
96
            $this->owner->SubsiteID = SubsiteState::singleton()->getSubsiteId();
97
        }
98
99
        parent::onBeforeWrite();
100
    }
101
102
    public function updateCMSFields(FieldList $fields)
103
    {
104
        $subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain');
105
        $subsitesMap = [];
106
        if ($subsites && $subsites->count()) {
107
            $subsitesToMap = $subsites->exclude('ID', $this->owner->SubsiteID);
108
            $subsitesMap = $subsitesToMap->map('ID', 'Title');
109
        }
110
111
        // Master page edit field (only allowed from default subsite to avoid inconsistent relationships)
112
        $isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite;
113
114
        if ($isDefaultSubsite && $subsitesMap) {
115
            $fields->addFieldToTab(
116
                'Root.Main',
117
                ToggleCompositeField::create(
118
                    'SubsiteOperations',
119
                    _t('SiteTreeSubsites.SubsiteOperations', 'Subsite Operations'),
120
                    [
121
                        new DropdownField('CopyToSubsiteID', _t(
122
                            'SiteTreeSubsites.CopyToSubsite',
123
                            'Copy page to subsite'
124
                        ), $subsitesMap),
125
                        new CheckboxField(
126
                            'CopyToSubsiteWithChildren',
127
                            _t('SiteTreeSubsites.CopyToSubsiteWithChildren', 'Include children pages?')
128
                        ),
129
                        $copyAction = new FormAction(
130
                            'copytosubsite',
131
                            _t('SiteTreeSubsites.CopyAction', 'Copy')
132
                        )
133
                    ]
134
                )->setHeadingLevel(4)
135
            );
136
137
            // @todo check if this needs re-implementation
138
//            $copyAction->includeDefaultJS(false);
139
        }
140
141
        // replace readonly link prefix
142
        $subsite = $this->owner->Subsite();
143
        $nested_urls_enabled = Config::inst()->get(SiteTree::class, 'nested_urls');
144
        if ($subsite && $subsite->exists()) {
145
            // Use baseurl from domain
146
            $baseLink = $subsite->absoluteBaseURL();
147
148
            // Add parent page if enabled
149
            if ($nested_urls_enabled && $this->owner->ParentID) {
150
                $baseLink = Controller::join_links(
151
                    $baseLink,
152
                    $this->owner->Parent()->RelativeLink(true)
153
                );
154
            }
155
156
            $urlsegment = $fields->dataFieldByName('URLSegment');
157
            $urlsegment->setURLPrefix($baseLink);
158
        }
159
    }
160
161
    /**
162
     * Does the basic duplication, but doesn't write anything
163
     * this means we can subclass this easier and do more complex
164
     * relation duplication.
165
     */
166
    public function duplicateToSubsitePrep($subsiteID)
167
    {
168
        if (is_object($subsiteID)) {
169
            $subsiteID = $subsiteID->ID;
170
        }
171
172
        $oldSubsite = SubsiteState::singleton()->getSubsiteId();
173
        if ($subsiteID) {
174
            Subsite::changeSubsite($subsiteID);
175
        } else {
176
            $subsiteID = $oldSubsite;
177
        }
178
        // doesn't write as we need to reset the SubsiteID, ParentID etc
179
        $clone = $this->owner->duplicate(false);
180
        $clone->CheckedPublicationDifferences = $clone->AddedToStage = true;
181
        $subsiteID = ($subsiteID ? $subsiteID : $oldSubsite);
182
        $clone->SubsiteID = $subsiteID;
183
        // We have no idea what the parentID should be, so as a workaround use the url-segment and subsite ID
184
        if ($this->owner->Parent()) {
185
            $parentSeg = $this->owner->Parent()->URLSegment;
186
            $newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first();
187
            if ($newParentPage) {
188
                $clone->ParentID = $newParentPage->ID;
189
            } else {
190
                // reset it to the top level, so the user can decide where to put it
191
                $clone->ParentID = 0;
192
            }
193
        }
194
        // MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module
195
        $clone->MasterPageID = $this->owner->ID;
196
        return $clone;
197
    }
198
199
    /**
200
     * Create a duplicate of this page and save it to another subsite
201
     * @param $subsiteID int|Subsite The Subsite to copy to, or its ID
202
     */
203
    public function duplicateToSubsite($subsiteID = null)
204
    {
205
        $clone = $this->owner->duplicateToSubsitePrep($subsiteID);
206
        $clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner);
207
        $clone->write();
208
        $clone->duplicateSubsiteRelations($this->owner);
209
        // new extension hooks which happens after write,
210
        // onAfterDuplicate isn't reliable due to
211
        // https://github.com/silverstripe/silverstripe-cms/issues/1253
212
        $clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner);
213
        return $clone;
214
    }
215
216
    /**
217
     * Duplicate relations using a static property to define
218
     * which ones we want to duplicate
219
     *
220
     * It may be that some relations are not diostinct to sub site so can stay
221
     * whereas others may need to be duplicated
222
     *
223
     */
224
    public function duplicateSubsiteRelations($originalPage)
225
    {
226
        $thisClass = $originalPage->ClassName;
227
        $relations = Config::inst()->get($thisClass, 'duplicate_to_subsite_relations');
228
229
        if ($relations && !empty($relations)) {
230
            foreach ($relations as $relation) {
231
                $items = $originalPage->$relation();
232
                foreach ($items as $item) {
233
                    $duplicateItem = $item->duplicate(false);
234
                    $duplicateItem->{$thisClass.'ID'} = $this->owner->ID;
235
                    $duplicateItem->write();
236
                }
237
            }
238
        }
239
    }
240
241
    /**
242
     * @return SiteConfig
243
     */
244
    public function alternateSiteConfig()
245
    {
246
        if (!$this->owner->SubsiteID) {
247
            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...
248
        }
249
        $sc = DataObject::get_one(SiteConfig::class, '"SubsiteID" = ' . $this->owner->SubsiteID);
250
        if (!$sc) {
251
            $sc = new SiteConfig();
252
            $sc->SubsiteID = $this->owner->SubsiteID;
253
            $sc->Title = _t('Subsite.SiteConfigTitle', 'Your Site Name');
254
            $sc->Tagline = _t('Subsite.SiteConfigSubtitle', 'Your tagline here');
255
            $sc->write();
256
        }
257
        return $sc;
258
    }
259
260
    /**
261
     * Only allow editing of a page if the member satisfies one of the following conditions:
262
     * - Is in a group which has access to the subsite this page belongs to
263
     * - Is in a group with edit permissions on the "main site"
264
     *
265
     * @param null $member
266
     * @return bool
267
     */
268
    public function canEdit($member = null)
269
    {
270
        if (!$member) {
271
            $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...
272
        }
273
274
        // Find the sites that this user has access to
275
        $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true, 'all', $member)->column('ID');
276
277
        if (!is_null($this->owner->SubsiteID)) {
278
            $subsiteID = $this->owner->SubsiteID;
279
        } else {
280
            // The relationships might not be available during the record creation when using a GridField.
281
            // In this case the related objects will have empty fields, and SubsiteID will not be available.
282
            //
283
            // We do the second best: fetch the likely SubsiteID from the session. The drawback is this might
284
            // make it possible to force relations to point to other (forbidden) subsites.
285
            $subsiteID = SubsiteState::singleton()->getSubsiteId();
286
        }
287
288
        // Return true if they have access to this object's site
289
        if (!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) {
290
            return false;
291
        }
292
    }
293
294
    /**
295
     * @param null $member
296
     * @return bool
297
     */
298 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...
299
    {
300
        if (!$member && $member !== false) {
301
            $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...
302
        }
303
304
        return $this->canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \SilverStripe\Security\Member::currentUser() on line 301 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...
305
    }
306
307
    /**
308
     * @param null $member
309
     * @return bool
310
     */
311 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...
312
    {
313
        if (!$member && $member !== false) {
314
            $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...
315
        }
316
317
        return $this->canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \SilverStripe\Security\Member::currentUser() on line 314 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...
318
    }
319
320
    /**
321
     * @param null $member
322
     * @return bool
323
     */
324 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...
325
    {
326
        if (!$member && $member !== false) {
327
            $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...
328
        }
329
330
        return $this->canEdit($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by \SilverStripe\Security\Member::currentUser() on line 327 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...
331
    }
332
333
    /**
334
     * Called by ContentController::init();
335
     * @param $controller
336
     */
337
    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...
338
    {
339
        $subsite = Subsite::currentSubsite();
340
341
        if ($subsite && $subsite->Theme) {
342
            SSViewer::set_themes(array_merge([$subsite->Theme], SSViewer::get_themes()));
343
        }
344
345
        if ($subsite && i18n::getData()->validate($subsite->Language)) {
346
            i18n::set_locale($subsite->Language);
347
        }
348
    }
349
350
    public function alternateAbsoluteLink()
351
    {
352
        // Generate the existing absolute URL and replace the domain with the subsite domain.
353
        // This helps deal with Link() returning an absolute URL.
354
        $url = Director::absoluteURL($this->owner->Link());
355
        if ($this->owner->SubsiteID) {
356
            $url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url);
357
        }
358
        return $url;
359
    }
360
361
    /**
362
     * Use the CMS domain for iframed CMS previews to prevent single-origin violations
363
     * and SSL cert problems.
364
     * @param null $action
365
     * @return string
366
     */
367
    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...
368
    {
369
        $url = Director::absoluteURL($this->owner->Link());
370
        if ($this->owner->SubsiteID) {
371
            $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...
372
        }
373
        return $url;
374
    }
375
376
    /**
377
     * Inject the subsite ID into the content so it can be used by frontend scripts.
378
     * @param $tags
379
     * @return string
380
     */
381
    public function MetaTags(&$tags)
382
    {
383
        if ($this->owner->SubsiteID) {
384
            $tags .= '<meta name="x-subsite-id" content="' . $this->owner->SubsiteID . "\" />\n";
385
        }
386
387
        return $tags;
388
    }
389
390
    public function augmentSyncLinkTracking()
391
    {
392
        // Set LinkTracking appropriately
393
        $links = HTTP::getLinksIn($this->owner->Content);
394
        $linkedPages = [];
395
396
        if ($links) {
397
            foreach ($links as $link) {
398
                if (substr($link, 0, strlen('http://')) == 'http://') {
399
                    $withoutHttp = substr($link, strlen('http://'));
400
                    if (strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) {
401
                        $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/'));
402
                        $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1);
403
404
                        $subsiteID = Subsite::getSubsiteIDForDomain($domain);
405
                        if ($subsiteID == 0) {
406
                            continue;
407
                        } // We have no idea what the domain for the main site is, so cant track links to it
408
409
                        $origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
410
                        Subsite::disable_subsite_filter(true);
411
                        $candidatePage = DataObject::get_one(
412
                            SiteTree::class,
413
                            "\"URLSegment\" = '" . Convert::raw2sql(urldecode($rest)) . "' AND \"SubsiteID\" = " . $subsiteID,
414
                            false
415
                        );
416
                        Subsite::disable_subsite_filter($origDisableSubsiteFilter);
417
418
                        if ($candidatePage) {
419
                            $linkedPages[] = $candidatePage->ID;
420
                        } else {
421
                            $this->owner->HasBrokenLink = true;
422
                        }
423
                    }
424
                }
425
            }
426
        }
427
428
        $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
429
    }
430
431
    /**
432
     * Ensure that valid url segments are checked within the correct subsite of the owner object,
433
     * even if the current subsiteID is set to some other subsite.
434
     *
435
     * @return null|bool Either true or false, or null to not influence result
436
     */
437
    public function augmentValidURLSegment()
438
    {
439
        // If this page is being filtered in the current subsite, then no custom validation query is required.
440
        $subsite = Subsite::$force_subsite ?: SubsiteState::singleton()->getSubsiteId();
441
        if (empty($this->owner->SubsiteID) || $subsite == $this->owner->SubsiteID) {
442
            return null;
443
        }
444
445
        // Backup forced subsite
446
        $prevForceSubsite = Subsite::$force_subsite;
447
        Subsite::$force_subsite = $this->owner->SubsiteID;
448
449
        // Repeat validation in the correct subsite
450
        $isValid = $this->owner->validURLSegment();
451
452
        // Restore
453
        Subsite::$force_subsite = $prevForceSubsite;
454
455
        return (bool)$isValid;
456
    }
457
458
    /**
459
     * Return a piece of text to keep DataObject cache keys appropriately specific
460
     */
461
    public function cacheKeyComponent()
462
    {
463
        return 'subsite-' . SubsiteState::singleton()->getSubsiteId();
464
    }
465
466
    /**
467
     * @param Member
468
     * @return boolean|null
469
     */
470
    public function canCreate($member = null)
471
    {
472
        // Typically called on a singleton, so we're not using the Subsite() relation
473
        $subsite = Subsite::currentSubsite();
474
        if ($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) {
475
            $blacklisted = explode(',', $subsite->PageTypeBlacklist);
476
            // All subclasses need to be listed explicitly
477
            if (in_array($this->owner->class, $blacklisted)) {
478
                return false;
479
            }
480
        }
481
    }
482
}
483