Completed
Push — 1.1 ( 1876b7...145b8e )
by Damian
04:01 queued 01:26
created

SiteTreeSubsites::canAddChildren()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 8
Ratio 100 %
Metric Value
dl 8
loc 8
rs 9.4286
cc 3
eloc 4
nc 2
nop 1
1
<?php
2
3
/**
4
 * Extension for the SiteTree object to add subsites support
5
 */
6
class SiteTreeSubsites extends DataExtension
7
{
8
    private static $has_one = array(
9
        'Subsite' => 'Subsite', // The subsite that this page belongs to
10
    );
11
12
    private static $many_many = array(
13
        'CrossSubsiteLinkTracking' => 'SiteTree' // Stored separately, as the logic for URL rewriting is different
14
    );
15
16
    private static $many_many_extraFields = array(
17
        "CrossSubsiteLinkTracking" => array("FieldName" => "Varchar")
18
    );
19
20
    public function isMainSite()
21
    {
22
        if ($this->owner->SubsiteID == 0) {
23
            return true;
24
        }
25
        return false;
26
    }
27
    
28
    /**
29
     * Update any requests to limit the results to the current site
30
     */
31
    public function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null)
32
    {
33
        if (Subsite::$disable_subsite_filter) {
34
            return;
35
        }
36
        if ($dataQuery->getQueryParam('Subsite.filter') === false) {
0 ignored issues
show
Bug introduced by
It seems like $dataQuery is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
37
            return;
38
        }
39
        
40
        // If you're querying by ID, ignore the sub-site - this is a bit ugly...
41
        // 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)) {
42
        if ($query->filtersOnID()) {
43
            return;
44
        }
45
46
        if (Subsite::$force_subsite) {
47
            $subsiteID = Subsite::$force_subsite;
48
        } else {
49
            /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID;
50
            else */$subsiteID = (int)Subsite::currentSubsiteID();
51
        }
52
53
        // The foreach is an ugly way of getting the first key :-)
54
        foreach ($query->getFrom() as $tableName => $info) {
55
            // The tableName should be SiteTree or SiteTree_Live...
56
            if (strpos($tableName, 'SiteTree') === false) {
57
                break;
58
            }
59
            $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)");
60
            break;
61
        }
62
    }
63
    
64
    public function onBeforeWrite()
65
    {
66 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...
67
            $this->owner->SubsiteID = Subsite::currentSubsiteID();
68
        }
69
        
70
        parent::onBeforeWrite();
71
    }
72
73
    public function updateCMSFields(FieldList $fields)
74
    {
75
        $subsites = Subsite::accessible_sites("CMS_ACCESS_CMSMain");
76
        $subsitesMap = array();
77
        if ($subsites && $subsites->Count()) {
78
            $subsitesMap = $subsites->map('ID', 'Title');
79
            unset($subsitesMap[$this->owner->SubsiteID]);
80
        }
81
82
        // Master page edit field (only allowed from default subsite to avoid inconsistent relationships)
83
        $isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite;
84
        if ($isDefaultSubsite && $subsitesMap) {
85
            $fields->addFieldToTab(
86
                'Root.Main',
87
                new DropdownField(
88
                    "CopyToSubsiteID",
89
                    _t('SiteTreeSubsites.CopyToSubsite', "Copy page to subsite"),
90
                    $subsitesMap,
91
                    ''
92
                )
93
            );
94
            $fields->addFieldToTab(
95
                'Root.Main',
96
                $copyAction = new InlineFormAction(
97
                    "copytosubsite",
0 ignored issues
show
Documentation introduced by
'copytosubsite' is of type string, but the function expects a object<The>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
98
                    _t('SiteTreeSubsites.CopyAction', "Copy")
99
                )
100
            );
101
            $copyAction->includeDefaultJS(false);
102
        }
103
104
        // replace readonly link prefix
105
        $subsite = $this->owner->Subsite();
106
        $nested_urls_enabled = Config::inst()->get('SiteTree', 'nested_urls');
107
        if ($subsite && $subsite->ID) {
108
            $baseUrl = Director::protocol() . $subsite->domain() . '/';
109
            $baseLink = Controller::join_links(
110
                $baseUrl,
111
                ($nested_urls_enabled && $this->owner->ParentID ? $this->owner->Parent()->RelativeLink(true) : null)
112
            );
113
            
114
            $urlsegment = $fields->dataFieldByName('URLSegment');
115
            $urlsegment->setURLPrefix($baseLink);
116
        }
117
    }
118
    
119
    public function alternateSiteConfig()
120
    {
121
        if (!$this->owner->SubsiteID) {
122
            return false;
123
        }
124
        $sc = DataObject::get_one('SiteConfig', '"SubsiteID" = ' . $this->owner->SubsiteID);
125
        if (!$sc) {
126
            $sc = new SiteConfig();
127
            $sc->SubsiteID = $this->owner->SubsiteID;
128
            $sc->Title = _t('Subsite.SiteConfigTitle', 'Your Site Name');
129
            $sc->Tagline = _t('Subsite.SiteConfigSubtitle', 'Your tagline here');
130
            $sc->write();
131
        }
132
        return $sc;
133
    }
134
    
135
    /**
136
     * Only allow editing of a page if the member satisfies one of the following conditions:
137
     * - Is in a group which has access to the subsite this page belongs to
138
     * - Is in a group with edit permissions on the "main site"
139
     * 
140
     * @return boolean
141
     */
142
    public function canEdit($member = null)
143
    {
144
        if (!$member) {
145
            $member = Member::currentUser();
146
        }
147
        
148
        // Find the sites that this user has access to
149
        $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true, 'all', $member)->column('ID');
150
151
        if (!is_null($this->owner->SubsiteID)) {
152
            $subsiteID = $this->owner->SubsiteID;
153
        } else {
154
            // The relationships might not be available during the record creation when using a GridField.
155
            // In this case the related objects will have empty fields, and SubsiteID will not be available.
156
            //
157
            // We do the second best: fetch the likely SubsiteID from the session. The drawback is this might
158
            // make it possible to force relations to point to other (forbidden) subsites.
159
            $subsiteID = Subsite::currentSubsiteID();
160
        }
161
162
        // Return true if they have access to this object's site
163
        if (!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) {
164
            return false;
165
        }
166
    }
167
    
168
    /**
169
     * @return boolean
170
     */
171 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...
172
    {
173
        if (!$member && $member !== false) {
174
            $member = Member::currentUser();
175
        }
176
        
177
        return $this->canEdit($member);
178
    }
179
    
180
    /**
181
     * @return boolean
182
     */
183 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...
184
    {
185
        if (!$member && $member !== false) {
186
            $member = Member::currentUser();
187
        }
188
        
189
        return $this->canEdit($member);
190
    }
191
    
192
    /**
193
     * @return boolean
194
     */
195 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...
196
    {
197
        if (!$member && $member !== false) {
198
            $member = Member::currentUser();
199
        }
200
201
        return $this->canEdit($member);
202
    }
203
204
    /**
205
     * Create a duplicate of this page and save it to another subsite
206
     * @param $subsiteID int|Subsite The Subsite to copy to, or its ID
207
     */
208
    public function duplicateToSubsite($subsiteID = null)
209
    {
210
        if (is_object($subsiteID)) {
211
            $subsite = $subsiteID;
212
            $subsiteID = $subsite->ID;
213
        } else {
214
            $subsite = DataObject::get_by_id('Subsite', $subsiteID);
0 ignored issues
show
Unused Code introduced by
$subsite 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...
215
        }
216
        
217
        $oldSubsite=Subsite::currentSubsiteID();
218
        if ($subsiteID) {
219
            Subsite::changeSubsite($subsiteID);
220
        } else {
221
            $subsiteID=$oldSubsite;
222
        }
223
224
        $page = $this->owner->duplicate(false);
225
226
        $page->CheckedPublicationDifferences = $page->AddedToStage = true;
227
        $subsiteID = ($subsiteID ? $subsiteID : $oldSubsite);
228
        $page->SubsiteID = $subsiteID;
229
230
        // MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module
231
        $page->MasterPageID = $this->owner->ID;
232
        $page->write();
233
234
        Subsite::changeSubsite($oldSubsite);
235
236
        return $page;
237
    }
238
239
    /**
240
     * Called by ContentController::init();
241
     */
242
    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...
243
    {
244
        $subsite = Subsite::currentSubsite();
245
246
        if ($subsite && $subsite->Theme) {
0 ignored issues
show
Documentation introduced by
The property Theme does not exist on object<Subsite>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
247
            Config::inst()->update('SSViewer', 'theme', Subsite::currentSubsite()->Theme);
0 ignored issues
show
Documentation introduced by
The property Theme does not exist on object<Subsite>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
248
        }
249
    }
250
251
    public function alternateAbsoluteLink()
252
    {
253
        // Generate the existing absolute URL and replace the domain with the subsite domain.
254
        // This helps deal with Link() returning an absolute URL.
255
        $url = Director::absoluteURL($this->owner->Link());
256
        if ($this->owner->SubsiteID) {
257
            $url = preg_replace('/\/\/[^\/]+\//', '//' .  $this->owner->Subsite()->domain() . '/', $url);
258
        }
259
        return $url;
260
    }
261
262
    /**
263
     * Use the CMS domain for iframed CMS previews to prevent single-origin violations
264
     * and SSL cert problems.
265
     */
266
    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...
267
    {
268
        $url = Director::absoluteURL($this->owner->Link());
269
        if ($this->owner->SubsiteID) {
270
            $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, HTTP::setGetVar() does only seem to accept string|null, did you maybe forget to handle an error condition?
Loading history...
271
        }
272
        return $url;
273
    }
274
275
    /**
276
     * Inject the subsite ID into the content so it can be used by frontend scripts.
277
     */
278
    public function MetaTags(&$tags)
279
    {
280
        if ($this->owner->SubsiteID) {
281
            $tags .= "<meta name=\"x-subsite-id\" content=\"" . $this->owner->SubsiteID . "\" />\n";
282
        }
283
284
        return $tags;
285
    }
286
287
    public function augmentSyncLinkTracking()
288
    {
289
        // Set LinkTracking appropriately
290
        $links = HTTP::getLinksIn($this->owner->Content);
291
        $linkedPages = array();
292
        
293
        if ($links) {
294
            foreach ($links as $link) {
295
                if (substr($link, 0, strlen('http://')) == 'http://') {
296
                    $withoutHttp = substr($link, strlen('http://'));
297
                    if (strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) {
298
                        $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/'));
299
                        $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1);
300
                    
301
                        $subsiteID = Subsite::getSubsiteIDForDomain($domain);
302
                        if ($subsiteID == 0) {
303
                            continue;
304
                        } // We have no idea what the domain for the main site is, so cant track links to it
305
306
                    $origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
307
                        Subsite::disable_subsite_filter(true);
308
                        $candidatePage = DataObject::get_one("SiteTree", "\"URLSegment\" = '" . Convert::raw2sql(urldecode($rest)) . "' AND \"SubsiteID\" = " . $subsiteID, false);
309
                        Subsite::disable_subsite_filter($origDisableSubsiteFilter);
310
                    
311
                        if ($candidatePage) {
312
                            $linkedPages[] = $candidatePage->ID;
313
                        } else {
314
                            $this->owner->HasBrokenLink = true;
315
                        }
316
                    }
317
                }
318
            }
319
        }
320
        
321
        $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages);
322
    }
323
    
324
    /**
325
     * Return a piece of text to keep DataObject cache keys appropriately specific
326
     */
327
    public function cacheKeyComponent()
328
    {
329
        return 'subsite-'.Subsite::currentSubsiteID();
330
    }
331
    
332
    /**
333
     * @param Member
334
     * @return boolean|null
335
     */
336
    public function canCreate($member = null)
337
    {
338
        // Typically called on a singleton, so we're not using the Subsite() relation
339
        $subsite = Subsite::currentSubsite();
340
        if ($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) {
0 ignored issues
show
Documentation introduced by
The property PageTypeBlacklist does not exist on object<Subsite>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
341
            $blacklisted = explode(',', $subsite->PageTypeBlacklist);
0 ignored issues
show
Documentation introduced by
The property PageTypeBlacklist does not exist on object<Subsite>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
342
            // All subclasses need to be listed explicitly
343
            if (in_array($this->owner->class, $blacklisted)) {
344
                return false;
345
            }
346
        }
347
    }
348
}
349