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

Subsite::domain()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 23
rs 8.7972
cc 4
eloc 11
nc 3
nop 0
1
<?php
2
/**
3
 * A dynamically created subsite. SiteTree objects can now belong to a subsite.
4
 * You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request.
5
 *
6
 * @package subsites
7
 */
8
class Subsite extends DataObject
9
{
10
    /**
11
     * @var $use_session_subsiteid Boolean Set to TRUE when using the CMS and FALSE
12
     * when browsing the frontend of a website. 
13
     * 
14
     * @todo Remove flag once the Subsite CMS works without session state,
15
     * similarly to the Translatable module.
16
     */
17
    public static $use_session_subsiteid = false;
18
19
    /**
20
     * @var boolean $disable_subsite_filter If enabled, bypasses the query decoration
21
     * to limit DataObject::get*() calls to a specific subsite. Useful for debugging.
22
     */
23
    public static $disable_subsite_filter = false;
24
    
25
    /**
26
     * Allows you to force a specific subsite ID, or comma separated list of IDs.
27
     * Only works for reading. An object cannot be written to more than 1 subsite.
28
     */
29
    public static $force_subsite = null;
30
31
    /**
32
     *
33
     * @var boolean
34
     */
35
    public static $write_hostmap = true;
36
    
37
    /**
38
     * Memory cache of accessible sites
39
     * 
40
     * @array
41
     */
42
    private static $_cache_accessible_sites = array();
43
44
    /**
45
     * Memory cache of subsite id for domains
46
     *
47
     * @var array
48
     */
49
    private static $_cache_subsite_for_domain = array();
50
51
    /**
52
     * @var array $allowed_themes Numeric array of all themes which are allowed to be selected for all subsites.
53
     * Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder
54
     * are listed.
55
     */
56
    private static $allowed_themes = array();
57
    
58
    /**
59
     * @var Boolean If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same.
60
     * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com')
61
     * in both TRUE or FALSE setting.
62
     */
63
    public static $strict_subdomain_matching = false;
64
65
    /**
66
     * @var boolean Respects the IsPublic flag when retrieving subsites
67
     */
68
    public static $check_is_public = true;
69
70
    /**
71
     * Set allowed themes
72
     * 
73
     * @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites.
74
     */
75
    public static function set_allowed_themes($themes)
76
    {
77
        self::$allowed_themes = $themes;
78
    }
79
    
80
    /**
81
     * Gets the subsite currently set in the session.
82
     *
83
     * @uses ControllerSubsites->controllerAugmentInit()
84
     * @return Subsite
85
     */
86
    public static function currentSubsite()
87
    {
88
        // get_by_id handles caching so we don't have to
89
        return DataObject::get_by_id('Subsite', self::currentSubsiteID());
90
    }
91
92
    /**
93
     * This function gets the current subsite ID from the session. It used in the backend so Ajax requests
94
     * use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain
95
     * directly from ModelAsController::getNestedController. Only gets Subsite instances which have their
96
     * {@link IsPublic} flag set to TRUE.
97
     *
98
     * You can simulate subsite access without creating virtual hosts by appending ?SubsiteID=<ID> to the request.
99
     *
100
     * @todo Pass $request object from controller so we don't have to rely on $_GET
101
     *
102
     * @param boolean $cache
0 ignored issues
show
Bug introduced by
There is no parameter named $cache. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
103
     * @return int ID of the current subsite instance
104
     */
105
    public static function currentSubsiteID()
106
    {
107
        $id = null;
108
109
        if (isset($_GET['SubsiteID'])) {
110
            $id = (int)$_GET['SubsiteID'];
111
        } elseif (Subsite::$use_session_subsiteid) {
112
            $id = Session::get('SubsiteID');
113
        }
114
115
        if ($id === null) {
116
            $id = self::getSubsiteIDForDomain();
117
        }
118
119
        return (int)$id;
120
    }
121
    
122
    /**
123
     * Switch to another subsite through storing the subsite identifier in the current PHP session.
124
     * Only takes effect when {@link Subsite::$use_session_subsiteid} is set to TRUE.
125
     *
126
     * @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself
127
     */
128
    public static function changeSubsite($subsite)
129
    {
130
        // Session subsite change only meaningful if the session is active.
131
        // Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID.
132
        if (!Subsite::$use_session_subsiteid) {
133
            return;
134
        }
135
136
        if (is_object($subsite)) {
137
            $subsiteID = $subsite->ID;
138
        } else {
139
            $subsiteID = $subsite;
140
        }
141
142
        Session::set('SubsiteID', (int)$subsiteID);
143
144
        // Set locale
145
        if (is_object($subsite) && $subsite->Language != '') {
0 ignored issues
show
Documentation introduced by
The property Language 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...
146
            $locale = i18n::get_locale_from_lang($subsite->Language);
0 ignored issues
show
Documentation introduced by
The property Language 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...
147
            if ($locale) {
148
                i18n::set_locale($locale);
149
            }
150
        }
151
152
        Permission::flush_permission_cache();
153
    }
154
    
155
    /**
156
     * Get a matching subsite for the given host, or for the current HTTP_HOST.
157
     * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string,
158
     * for example matching all subdomains on *.example.com with one subsite,
159
     * and all subdomains on *.example.org on another.
160
     * 
161
     * @param $host The host to find the subsite for.  If not specified, $_SERVER['HTTP_HOST'] is used.
162
     * @return int Subsite ID
163
     */
164
    public static function getSubsiteIDForDomain($host = null, $checkPermissions = true)
0 ignored issues
show
Unused Code introduced by
The parameter $checkPermissions 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...
165
    {
166
        if ($host == null && isset($_SERVER['HTTP_HOST'])) {
167
            $host = $_SERVER['HTTP_HOST'];
168
        }
169
170
        $matchingDomains = null;
171
        $cacheKey = null;
172
        if ($host) {
173
            if (!self::$strict_subdomain_matching) {
174
                $host = preg_replace('/^www\./', '', $host);
175
            }
176
177
            $cacheKey = implode('_', array($host, Member::currentUserID(), self::$check_is_public));
178
            if (isset(self::$_cache_subsite_for_domain[$cacheKey])) {
179
                return self::$_cache_subsite_for_domain[$cacheKey];
180
            }
181
182
            $SQL_host = Convert::raw2sql($host);
183
            $matchingDomains = DataObject::get(
184
                "SubsiteDomain",
185
                "'$SQL_host' LIKE replace(\"SubsiteDomain\".\"Domain\",'*','%')",
186
                "\"IsPrimary\" DESC"
187
            )->innerJoin('Subsite', "\"Subsite\".\"ID\" = \"SubsiteDomain\".\"SubsiteID\" AND \"Subsite\".\"IsPublic\"=1");
188
        }
189
190
        if ($matchingDomains && $matchingDomains->Count()) {
191
            $subsiteIDs = array_unique($matchingDomains->column('SubsiteID'));
192
            $subsiteDomains = array_unique($matchingDomains->column('Domain'));
193
            if (sizeof($subsiteIDs) > 1) {
194
                throw new UnexpectedValueException(sprintf(
195
                    "Multiple subsites match on '%s': %s",
196
                    $host,
197
                    implode(',', $subsiteDomains)
198
                ));
199
            }
200
201
            $subsiteID = $subsiteIDs[0];
202
        } elseif ($default = DataObject::get_one('Subsite', "\"DefaultSite\" = 1")) {
203
            // Check for a 'default' subsite
204
            $subsiteID = $default->ID;
205
        } else {
206
            // Default subsite id = 0, the main site
207
            $subsiteID = 0;
208
        }
209
210
        if ($cacheKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheKey of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
211
            self::$_cache_subsite_for_domain[$cacheKey] = $subsiteID;
212
        }
213
214
        return $subsiteID;
215
    }
216
217
    /**
218
     * 
219
     * @param string $className
220
     * @param string $filter
221
     * @param string $sort
222
     * @param string $join
223
     * @param string $limit
224
     * @return DataList
225
     */
226
    public static function get_from_all_subsites($className, $filter = "", $sort = "", $join = "", $limit = "")
227
    {
228
        $result = DataObject::get($className, $filter, $sort, $join, $limit);
229
        $result = $result->setDataQueryParam('Subsite.filter', false);
230
        return $result;
231
    }
232
233
    /**
234
     * Disable the sub-site filtering; queries will select from all subsites
235
     */
236
    public static function disable_subsite_filter($disabled = true)
237
    {
238
        self::$disable_subsite_filter = $disabled;
239
    }
240
    
241
    /**
242
     * Flush caches on database reset
243
     */
244
    public static function on_db_reset()
245
    {
246
        self::$_cache_accessible_sites = array();
247
        self::$_cache_subsite_for_domain = array();
248
    }
249
    
250
    /**
251
     * Return all subsites, regardless of permissions (augmented with main site).
252
     *
253
     * @return SS_List List of {@link Subsite} objects (DataList or ArrayList).
254
     */
255
    public static function all_sites($includeMainSite = true, $mainSiteTitle = "Main site")
256
    {
257
        $subsites = Subsite::get();
258
259 View Code Duplication
        if ($includeMainSite) {
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...
260
            $subsites = $subsites->toArray();
261
262
            $mainSite = new Subsite();
263
            $mainSite->Title = $mainSiteTitle;
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<Subsite>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
264
            array_unshift($subsites, $mainSite);
265
266
            $subsites = ArrayList::create($subsites);
267
        }
268
269
        return $subsites;
270
    }
271
272
    /*
273
     * Returns an ArrayList of the subsites accessible to the current user.
274
     * It's enough for any section to be accessible for the site to be included.
275
     *
276
     * @return ArrayList of {@link Subsite} instances.
277
     */
278
    public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = "Main site", $member = null)
279
    {
280
        // Rationalise member arguments
281
        if (!$member) {
282
            $member = Member::currentUser();
283
        }
284
        if (!$member) {
285
            return new ArrayList();
286
        }
287
        if (!is_object($member)) {
288
            $member = DataObject::get_by_id('Member', $member);
289
        }
290
291
        $subsites = new ArrayList();
292
293
        // Collect subsites for all sections.
294
        $menu = CMSMenu::get_viewable_menu_items();
295
        foreach ($menu as $candidate) {
296
            if ($candidate->controller) {
297
                $accessibleSites = singleton($candidate->controller)->sectionSites(
298
                    $includeMainSite,
299
                    $mainSiteTitle,
300
                    $member
301
                );
302
303
                // Replace existing keys so no one site appears twice.
304
                $subsites->merge($accessibleSites);
305
            }
306
        }
307
308
        $subsites->removeDuplicates();
309
310
        return $subsites;
311
    }
312
313
    /**
314
     * Return the subsites that the current user can access by given permission.
315
     * Sites will only be included if they have a Title.
316
     *
317
     * @param $permCode array|string Either a single permission code or an array of permission codes.
318
     * @param $includeMainSite If true, the main site will be included if appropriate.
319
     * @param $mainSiteTitle The label to give to the main site
320
     * @param $member
321
     * @return DataList of {@link Subsite} instances
322
     */
323
    public static function accessible_sites($permCode, $includeMainSite = true, $mainSiteTitle = "Main site", $member = null)
324
    {
325
        // Rationalise member arguments
326
        if (!$member) {
327
            $member = Member::currentUser();
328
        }
329
        if (!$member) {
330
            return new ArrayList();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \ArrayList(); (ArrayList) is incompatible with the return type documented by Subsite::accessible_sites of type DataList.

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...
331
        }
332
        if (!is_object($member)) {
333
            $member = DataObject::get_by_id('Member', $member);
334
        }
335
336
        // Rationalise permCode argument 
337
        if (is_array($permCode)) {
338
            $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'";
339
        } else {
340
            $SQL_codes = "'" . Convert::raw2sql($permCode) . "'";
341
        }
342
        
343
        // Cache handling
344
        $cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle;
345
        if (isset(self::$_cache_accessible_sites[$cacheKey])) {
346
            return self::$_cache_accessible_sites[$cacheKey];
347
        }
348
349
        $subsites = DataList::create('Subsite')
350
            ->where("\"Subsite\".\"Title\" != ''")
351
            ->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
352
            ->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
353
            ->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
354
            ->innerJoin('Permission', "\"Group\".\"ID\"=\"Permission\".\"GroupID\" AND \"Permission\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')");
355
356
        if (!$subsites) {
357
            $subsites = new ArrayList();
358
        }
359
360
        $rolesSubsites = DataList::create('Subsite')
361
            ->where("\"Subsite\".\"Title\" != ''")
362
            ->leftJoin('Group_Subsites', "\"Group_Subsites\".\"SubsiteID\" = \"Subsite\".\"ID\"")
363
            ->innerJoin('Group', "\"Group\".\"ID\" = \"Group_Subsites\".\"GroupID\" OR \"Group\".\"AccessAllSubsites\" = 1")
364
            ->innerJoin('Group_Members', "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID")
365
            ->innerJoin('Group_Roles', "\"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"")
366
            ->innerJoin('PermissionRole', "\"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"")
367
            ->innerJoin('PermissionRoleCode', "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" AND \"PermissionRoleCode\".\"Code\" IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')");
368
369
        if (!$subsites && $rolesSubsites) {
370
            return $rolesSubsites;
371
        }
372
373
        $subsites = new ArrayList($subsites->toArray());
374
375
        if ($rolesSubsites) {
376
            foreach ($rolesSubsites as $subsite) {
377
                if (!$subsites->find('ID', $subsite->ID)) {
378
                    $subsites->push($subsite);
379
                }
380
            }
381
        }
382
383
        if ($includeMainSite) {
384
            if (!is_array($permCode)) {
385
                $permCode = array($permCode);
386
            }
387 View Code Duplication
            if (self::hasMainSitePermission($member, $permCode)) {
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...
388
                $subsites=$subsites->toArray();
389
                
390
                $mainSite = new Subsite();
391
                $mainSite->Title = $mainSiteTitle;
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<Subsite>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
392
                array_unshift($subsites, $mainSite);
393
                $subsites=ArrayList::create($subsites);
394
            }
395
        }
396
        
397
        self::$_cache_accessible_sites[$cacheKey] = $subsites;
398
399
        return $subsites;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $subsites; (ArrayList) is incompatible with the return type documented by Subsite::accessible_sites of type DataList.

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...
400
    }
401
    
402
    /**
403
     * Write a host->domain map to subsites/host-map.php
404
     *
405
     * This is used primarily when using subsites in conjunction with StaticPublisher
406
     *
407
     * @param string $file - filepath of the host map to be written
408
     * @return void
409
     */
410
    public static function writeHostMap($file = null)
411
    {
412
        if (!self::$write_hostmap) {
413
            return;
414
        }
415
        
416
        if (!$file) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
417
            $file = Director::baseFolder().'/subsites/host-map.php';
418
        }
419
        $hostmap = array();
420
        
421
        $subsites = DataObject::get('Subsite');
422
        
423
        if ($subsites) {
424
            foreach ($subsites as $subsite) {
425
                $domains = $subsite->Domains();
426
                if ($domains) {
427
                    foreach ($domains as $domain) {
428
                        $domainStr = $domain->Domain;
429
                        if (!self::$strict_subdomain_matching) {
430
                            $domainStr = preg_replace('/^www\./', '', $domainStr);
431
                        }
432
                        $hostmap[$domainStr] = $subsite->domain();
433
                    }
434
                }
435
                if ($subsite->DefaultSite) {
436
                    $hostmap['default'] = $subsite->domain();
437
                }
438
            }
439
        }
440
        
441
        $data = "<?php \n";
442
        $data .= "// Generated by Subsite::writeHostMap() on " . date('d/M/y') . "\n";
443
        $data .= '$subsiteHostmap = ' . var_export($hostmap, true) . ';';
444
445
        if (is_writable(dirname($file)) || is_writable($file)) {
446
            file_put_contents($file, $data);
447
        }
448
    }
449
    
450
    /**
451
     * Checks if a member can be granted certain permissions, regardless of the subsite context.
452
     * Similar logic to {@link Permission::checkMember()}, but only returns TRUE
453
     * if the member is part of a group with the "AccessAllSubsites" flag set.
454
     * If more than one permission is passed to the method, at least one of them must
455
     * be granted for if to return TRUE.
456
     * 
457
     * @todo Allow permission inheritance through group hierarchy.
458
     * 
459
     * @param Member Member to check against. Defaults to currently logged in member
460
     * @param Array Permission code strings. Defaults to "ADMIN".
461
     * @return boolean
462
     */
463
    public static function hasMainSitePermission($member = null, $permissionCodes = array('ADMIN'))
464
    {
465
        if (!is_array($permissionCodes)) {
466
            user_error('Permissions must be passed to Subsite::hasMainSitePermission as an array', E_USER_ERROR);
467
        }
468
469
        if (!$member && $member !== false) {
470
            $member = Member::currentUser();
471
        }
472
473
        if (!$member) {
474
            return false;
475
        }
476
        
477
        if (!in_array("ADMIN", $permissionCodes)) {
478
            $permissionCodes[] = "ADMIN";
479
        }
480
481
        $SQLa_perm = Convert::raw2sql($permissionCodes);
482
        $SQL_perms = join("','", $SQLa_perm);
483
        $memberID = (int)$member->ID;
484
        
485
        // Count this user's groups which can access the main site
486
        $groupCount = DB::query("
487
			SELECT COUNT(\"Permission\".\"ID\")
488
			FROM \"Permission\"
489
			INNER JOIN \"Group\" ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1
490
			INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Permission\".\"GroupID\"
491
			WHERE \"Permission\".\"Code\" IN ('$SQL_perms')
492
			AND \"MemberID\" = {$memberID}
493
		")->value();
494
495
        // Count this user's groups which have a role that can access the main site
496
        $roleCount = DB::query("
497
			SELECT COUNT(\"PermissionRoleCode\".\"ID\")
498
			FROM \"Group\"
499
			INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\"
500
			INNER JOIN \"Group_Roles\" ON \"Group_Roles\".\"GroupID\"=\"Group\".\"ID\"
501
			INNER JOIN \"PermissionRole\" ON \"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\"
502
			INNER JOIN \"PermissionRoleCode\" ON \"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\"
503
			WHERE \"PermissionRoleCode\".\"Code\" IN ('$SQL_perms')
504
			AND \"Group\".\"AccessAllSubsites\" = 1
505
			AND \"MemberID\" = {$memberID}
506
		")->value();
507
508
        // There has to be at least one that allows access.
509
        return ($groupCount + $roleCount > 0);
510
    }
511
    
512
    /**
513
     *
514
     * @var array
515
     */
516
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
517
        'Title' => 'Varchar(255)',
518
        'RedirectURL' => 'Varchar(255)',
519
        'DefaultSite' => 'Boolean',
520
        'Theme' => 'Varchar',
521
        'Language' => 'Varchar(6)',
522
523
        // Used to hide unfinished/private subsites from public view.
524
        // If unset, will default to true
525
        'IsPublic' => 'Boolean',
526
        
527
        // Comma-separated list of disallowed page types
528
        'PageTypeBlacklist' => 'Text',
529
    );
530
531
    /**
532
     *
533
     * @var array
534
     */
535
    private static $has_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
536
        'Domains' => 'SubsiteDomain',
537
    );
538
    
539
    /**
540
     *
541
     * @var array
542
     */
543
    private static $belongs_many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
544
        "Groups" => "Group",
545
    );
546
547
    /**
548
     *
549
     * @var array
550
     */
551
    private static $defaults = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
552
        'IsPublic' => 1
553
    );
554
555
    /**
556
     *
557
     * @var array
558
     */
559
    private static $searchable_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
560
        'Title',
561
        'Domains.Domain',
562
        'IsPublic',
563
    );
564
    
565
    /**
566
     *
567
     * @var string
568
     */
569
    private static $default_sort = "\"Title\" ASC";
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
570
    
571
    /**
572
     * @todo Possible security issue, don't grant edit permissions to everybody.
573
     * @return boolean
574
     */
575
    public function canEdit($member = false)
576
    {
577
        return true;
578
    }
579
580
    /**
581
     * Show the configuration fields for each subsite
582
     * 
583
     * @return FieldList
584
     */
585
    public function getCMSFields()
586
    {
587
        if ($this->ID!=0) {
588
            $domainTable = new GridField(
589
                "Domains",
590
                _t('Subsite.DomainsListTitle', "Domains"),
591
                $this->Domains(),
0 ignored issues
show
Documentation Bug introduced by
The method Domains does not exist on object<Subsite>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
592
                GridFieldConfig_RecordEditor::create(10)
593
            );
594
        } else {
595
            $domainTable = new LiteralField(
596
                'Domains',
597
                '<p>'._t('Subsite.DOMAINSAVEFIRST', 'You can only add domains after saving for the first time').'</p>'
598
            );
599
        }
600
            
601
        $languageSelector = new DropdownField(
602
            'Language',
603
            $this->fieldLabel('Language'),
604
            i18n::get_common_locales()
605
        );
606
        
607
        $pageTypeMap = array();
608
        $pageTypes = SiteTree::page_type_classes();
609
        foreach ($pageTypes as $pageType) {
610
            $pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name();
611
        }
612
        asort($pageTypeMap);
613
614
        $fields = new FieldList(
615
            $subsiteTabs = new TabSet('Root',
616
                new Tab(
617
                    'Configuration',
618
                    _t('Subsite.TabTitleConfig', 'Configuration'),
619
                    new HeaderField($this->getClassName() . ' configuration', 2),
620
                    new TextField('Title', $this->fieldLabel('Title'), $this->Title),
0 ignored issues
show
Documentation introduced by
The property Title 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...
621
                    
622
                    new HeaderField(
623
                        _t('Subsite.DomainsHeadline', "Domains for this subsite")
624
                    ),
625
                    $domainTable,
626
                    $languageSelector,
627
                    // new TextField('RedirectURL', 'Redirect to URL', $this->RedirectURL),
628
                    new CheckboxField('DefaultSite', $this->fieldLabel('DefaultSite'), $this->DefaultSite),
0 ignored issues
show
Bug introduced by
The property DefaultSite does not seem to exist. Did you mean defaults?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
629
                    new CheckboxField('IsPublic', $this->fieldLabel('IsPublic'), $this->IsPublic),
0 ignored issues
show
Documentation introduced by
The property IsPublic 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...
630
631
                    new DropdownField('Theme', $this->fieldLabel('Theme'), $this->allowedThemes(), $this->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...
632
                    
633
                    
634
                    new LiteralField(
635
                        'PageTypeBlacklistToggle',
636
                        sprintf(
637
                            '<div class="field"><a href="#" id="PageTypeBlacklistToggle">%s</a></div>',
638
                            _t('Subsite.PageTypeBlacklistField', 'Disallow page types?')
639
                        )
640
                    ),
641
                    new CheckboxSetField(
642
                        'PageTypeBlacklist',
643
                        false,
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string|null.

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...
644
                        $pageTypeMap
645
                    )
646
                )
647
            ),
648
            new HiddenField('ID', '', $this->ID),
649
            new HiddenField('IsSubsite', '', 1)
650
        );
651
652
        $subsiteTabs->addExtraClass('subsite-model');
653
654
        $this->extend('updateCMSFields', $fields);
655
        return $fields;
656
    }
657
    
658
    /**
659
     * 
660
     * @param boolean $includerelations
661
     * @return array
662
     */
663
    public function fieldLabels($includerelations = true)
664
    {
665
        $labels = parent::fieldLabels($includerelations);
666
        $labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name');
667
        $labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL');
668
        $labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site');
669
        $labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme');
670
        $labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language');
671
        $labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access');
672
        $labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist');
673
        $labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain');
674
        $labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain');
675
676
        return $labels;
677
    }
678
679
    /**
680
     * 
681
     * @return array
682
     */
683
    public function summaryFields()
684
    {
685
        return array(
686
            'Title' => $this->fieldLabel('Title'),
687
            'PrimaryDomain' => $this->fieldLabel('PrimaryDomain'),
688
            'IsPublic' => _t('Subsite.IsPublicHeaderField', 'Active subsite'),
689
        );
690
    }
691
    
692
    /**
693
     * Return the themes that can be used with this subsite, as an array of themecode => description
694
     * 
695
     * @return array
696
     */
697
    public function allowedThemes()
698
    {
699
        if ($themes = $this->stat('allowed_themes')) {
700
            return ArrayLib::valuekey($themes);
701
        } else {
702
            $themes = array();
703
            if (is_dir('../themes/')) {
704
                foreach (scandir('../themes/') as $theme) {
705
                    if ($theme[0] == '.') {
706
                        continue;
707
                    }
708
                    $theme = strtok($theme, '_');
709
                    $themes[$theme] = $theme;
710
                }
711
                ksort($themes);
712
            }
713
            return $themes;
714
        }
715
    }
716
717
    /**
718
     * @return string Current locale of the subsite
719
     */
720
    public function getLanguage()
721
    {
722
        if ($this->getField('Language')) {
723
            return $this->getField('Language');
724
        } else {
725
            return i18n::get_locale();
726
        }
727
    }
728
729
    /**
730
     * 
731
     * @return ValidationResult
732
     */
733
    public function validate()
734
    {
735
        $result = parent::validate();
736
        if (!$this->Title) {
0 ignored issues
show
Documentation introduced by
The property Title 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...
737
            $result->error(_t('Subsite.ValidateTitle', 'Please add a "Title"'));
738
        }
739
        return $result;
740
    }
741
742
    /**
743
     * Whenever a Subsite is written, rewrite the hostmap
744
     *
745
     * @return void
746
     */
747
    public function onAfterWrite()
748
    {
749
        Subsite::writeHostMap();
750
        parent::onAfterWrite();
751
    }
752
    
753
    /**
754
     * Return the primary domain of this site. Tries to "normalize" the domain name,
755
     * by replacing potential wildcards.
756
     * 
757
     * @return string The full domain name of this subsite (without protocol prefix)
758
     */
759
    public function domain()
760
    {
761
        if ($this->ID) {
762
            $domains = DataObject::get("SubsiteDomain", "\"SubsiteID\" = $this->ID", "\"IsPrimary\" DESC", "", 1);
763
            if ($domains && $domains->Count()>0) {
764
                $domain = $domains->First()->Domain;
765
                // If there are wildcards in the primary domain (not recommended), make some
766
                // educated guesses about what to replace them with:
767
                $domain = preg_replace('/\.\*$/', ".$_SERVER[HTTP_HOST]", $domain);
768
                // Default to "subsite." prefix for first wildcard
769
                // TODO Whats the significance of "subsite" in this context?!
770
                $domain = preg_replace('/^\*\./', "subsite.", $domain);
771
                // *Only* removes "intermediate" subdomains, so 'subdomain.www.domain.com' becomes 'subdomain.domain.com'
772
                $domain = str_replace('.www.', '.', $domain);
773
                
774
                return $domain;
775
            }
776
            
777
        // SubsiteID = 0 is often used to refer to the main site, just return $_SERVER['HTTP_HOST']
778
        } else {
779
            return $_SERVER['HTTP_HOST'];
780
        }
781
    }
782
    
783
    /**
784
     * 
785
     * @return string - The full domain name of this subsite (without protocol prefix)
786
     */
787
    public function getPrimaryDomain()
788
    {
789
        return $this->domain();
790
    }
791
792
    /**
793
     * 
794
     * @return string 
795
     */
796
    public function absoluteBaseURL()
797
    {
798
        return "http://" . $this->domain() . Director::baseURL();
799
    }
800
801
    /**
802
     * @todo getClassName is redundant, already stored as a database field?
803
     */
804
    public function getClassName()
805
    {
806
        return $this->class;
807
    }
808
809
    /**
810
     * Javascript admin action to duplicate this subsite
811
     * 
812
     * @return string - javascript 
813
     */
814
    public function adminDuplicate()
815
    {
816
        $newItem = $this->duplicate();
817
        $message = _t(
818
            'Subsite.CopyMessage',
819
            'Created a copy of {title}',
820
            array('title' => Convert::raw2js($this->Title))
0 ignored issues
show
Documentation introduced by
The property Title 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...
Documentation introduced by
array('title' => \Convert::raw2js($this->Title)) is of type array<string,array|strin...title":"array|string"}>, but the function expects a string.

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...
821
        );
822
        
823
        return <<<JS
824
			statusMessage($message, 'good');
825
			$('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID');
826
JS;
827
    }
828
829
    /**
830
     * Make this subsite the current one
831
     */
832
    public function activate()
833
    {
834
        Subsite::changeSubsite($this);
835
    }
836
837
    /**
838
     * 
839
     * @param array $permissionCodes
840
     * @return DataList
841
     */
842
    public function getMembersByPermission($permissionCodes = array('ADMIN'))
843
    {
844
        if (!is_array($permissionCodes)) {
845
            user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR);
846
        }
847
        $SQL_permissionCodes = Convert::raw2sql($permissionCodes);
848
849
        $SQL_permissionCodes = join("','", $SQL_permissionCodes);
850
851
        return DataObject::get(
852
            'Member',
853
            "\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')",
854
            '',
855
            "LEFT JOIN \"Group_Members\" ON \"Member\".\"ID\" = \"Group_Members\".\"MemberID\"
856
			LEFT JOIN \"Group\" ON \"Group\".\"ID\" = \"Group_Members\".\"GroupID\"
857
			LEFT JOIN \"Permission\" ON \"Permission\".\"GroupID\" = \"Group\".\"ID\""
858
        );
859
    }
860
861
    /**
862
     * Duplicate this subsite
863
     */
864
    public function duplicate($doWrite = true)
865
    {
866
        $duplicate = parent::duplicate($doWrite);
867
868
        $oldSubsiteID = Session::get('SubsiteID');
869
        self::changeSubsite($this->ID);
870
871
        /*
872
         * Copy data from this object to the given subsite. Does this using an iterative depth-first search.
873
         * This will make sure that the new parents on the new subsite are correct, and there are no funny
874
         * issues with having to check whether or not the new parents have been added to the site tree
875
         * when a page, etc, is duplicated
876
         */
877
        $stack = array(array(0,0));
878
        while (count($stack) > 0) {
879
            list($sourceParentID, $destParentID) = array_pop($stack);
880
            $children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", '');
881
882
            if ($children) {
883
                foreach ($children as $child) {
884
                    self::changeSubsite($duplicate->ID); //Change to destination subsite
885
886
                    $childClone = $child->duplicateToSubsite($duplicate, false);
887
                    $childClone->ParentID = $destParentID;
888
                    $childClone->writeToStage('Stage');
889
                    $childClone->publish('Stage', 'Live');
890
891
                    self::changeSubsite($this->ID); //Change Back to this subsite
892
893
                    array_push($stack, array($child->ID, $childClone->ID));
894
                }
895
            }
896
        }
897
898
        self::changeSubsite($oldSubsiteID);
899
900
        return $duplicate;
901
    }
902
}
903