Check for code that has been commented out.
1 | <?php |
||
2 | |||
3 | namespace SilverStripe\Subsites\Model; |
||
4 | |||
5 | use SilverStripe\Admin\CMSMenu; |
||
6 | use SilverStripe\CMS\Model\SiteTree; |
||
7 | use SilverStripe\Control\Director; |
||
8 | use SilverStripe\Control\Session; |
||
9 | use SilverStripe\Core\Convert; |
||
10 | use SilverStripe\Core\Injector\Injector; |
||
11 | use SilverStripe\Core\Manifest\ModuleLoader; |
||
12 | use SilverStripe\Dev\Deprecation; |
||
13 | use SilverStripe\Forms\CheckboxSetField; |
||
14 | use SilverStripe\Forms\DropdownField; |
||
15 | use SilverStripe\Forms\FieldList; |
||
16 | use SilverStripe\Forms\GridField\GridField; |
||
17 | use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; |
||
18 | use SilverStripe\Forms\HiddenField; |
||
19 | use SilverStripe\Forms\Tab; |
||
20 | use SilverStripe\Forms\ToggleCompositeField; |
||
21 | use SilverStripe\ORM\ArrayLib; |
||
22 | use SilverStripe\ORM\ArrayList; |
||
23 | use SilverStripe\ORM\DB; |
||
24 | use SilverStripe\ORM\DataList; |
||
25 | use SilverStripe\ORM\DataObject; |
||
26 | use SilverStripe\ORM\SS_List; |
||
27 | use SilverStripe\Security\Group; |
||
28 | use SilverStripe\Security\Member; |
||
29 | use SilverStripe\Security\Permission; |
||
30 | use SilverStripe\Security\Security; |
||
31 | use SilverStripe\Subsites\State\SubsiteState; |
||
32 | use SilverStripe\Versioned\Versioned; |
||
33 | use SilverStripe\i18n\Data\Intl\IntlLocales; |
||
34 | use SilverStripe\i18n\i18n; |
||
35 | use UnexpectedValueException; |
||
36 | |||
37 | /** |
||
38 | * A dynamically created subsite. SiteTree objects can now belong to a subsite. |
||
39 | * You can simulate subsite access without setting up virtual hosts by appending ?SubsiteID=<ID> to the request. |
||
40 | * |
||
41 | * @package subsites |
||
42 | */ |
||
43 | class Subsite extends DataObject |
||
44 | { |
||
45 | |||
46 | private static $table_name = 'Subsite'; |
||
47 | |||
48 | /** |
||
49 | * @var boolean $disable_subsite_filter If enabled, bypasses the query decoration |
||
50 | * to limit DataObject::get*() calls to a specific subsite. Useful for debugging. Note that |
||
51 | * for now this is left as a public static property to avoid having to nest and mutate the |
||
52 | * configuration manifest. |
||
53 | */ |
||
54 | public static $disable_subsite_filter = false; |
||
55 | |||
56 | /** |
||
57 | * Allows you to force a specific subsite ID, or comma separated list of IDs. |
||
58 | * Only works for reading. An object cannot be written to more than 1 subsite. |
||
59 | * |
||
60 | * @deprecated 2.0.0..3.0.0 Use SubsiteState::singleton()->withState() instead. |
||
61 | */ |
||
62 | public static $force_subsite = null; |
||
63 | |||
64 | /** |
||
65 | * Whether to write a host-map.php file |
||
66 | * |
||
67 | * @config |
||
68 | * @var boolean |
||
69 | */ |
||
70 | private static $write_hostmap = true; |
||
71 | |||
72 | /** |
||
73 | * Memory cache of accessible sites |
||
74 | * |
||
75 | * @array |
||
76 | */ |
||
77 | protected static $cache_accessible_sites = []; |
||
78 | |||
79 | /** |
||
80 | * Memory cache of subsite id for domains |
||
81 | * |
||
82 | * @var array |
||
83 | */ |
||
84 | protected static $cache_subsite_for_domain = []; |
||
85 | |||
86 | /** |
||
87 | * Numeric array of all themes which are allowed to be selected for all subsites. |
||
88 | * Corresponds to subfolder names within the /themes folder. By default, all themes contained in this folder |
||
89 | * are listed. |
||
90 | * |
||
91 | * @var array |
||
92 | */ |
||
93 | protected static $allowed_themes = []; |
||
94 | |||
95 | /** |
||
96 | * If set to TRUE, don't assume 'www.example.com' and 'example.com' are the same. |
||
97 | * Doesn't affect wildcard matching, so '*.example.com' will match 'www.example.com' (but not 'example.com') |
||
98 | * in both TRUE or FALSE setting. |
||
99 | * |
||
100 | * @config |
||
101 | * @var boolean |
||
102 | */ |
||
103 | private static $strict_subdomain_matching = false; |
||
104 | |||
105 | /** |
||
106 | * Respects the IsPublic flag when retrieving subsites |
||
107 | * |
||
108 | * @config |
||
109 | * @var boolean |
||
110 | */ |
||
111 | private static $check_is_public = true; |
||
112 | |||
113 | /*** @return array |
||
114 | */ |
||
115 | private static $summary_fields = [ |
||
116 | 'Title', |
||
117 | 'PrimaryDomain', |
||
118 | 'IsPublic' |
||
119 | ]; |
||
120 | |||
121 | /** |
||
122 | * @var array |
||
123 | */ |
||
124 | private static $db = [ |
||
125 | 'Title' => 'Varchar(255)', |
||
126 | 'RedirectURL' => 'Varchar(255)', |
||
127 | 'DefaultSite' => 'Boolean', |
||
128 | 'Theme' => 'Varchar', |
||
129 | 'Language' => 'Varchar(6)', |
||
130 | |||
131 | // Used to hide unfinished/private subsites from public view. |
||
132 | // If unset, will default to true |
||
0 ignored issues
–
show
|
|||
133 | 'IsPublic' => 'Boolean', |
||
134 | |||
135 | // Comma-separated list of disallowed page types |
||
136 | 'PageTypeBlacklist' => 'Text', |
||
137 | ]; |
||
138 | |||
139 | /** |
||
140 | * @var array |
||
141 | */ |
||
142 | private static $has_many = [ |
||
143 | 'Domains' => SubsiteDomain::class, |
||
144 | ]; |
||
145 | |||
146 | /** |
||
147 | * @var array |
||
148 | */ |
||
149 | private static $belongs_many_many = [ |
||
150 | 'Groups' => Group::class, |
||
151 | ]; |
||
152 | |||
153 | /** |
||
154 | * @var array |
||
155 | */ |
||
156 | private static $defaults = [ |
||
157 | 'IsPublic' => 1 |
||
158 | ]; |
||
159 | |||
160 | /** |
||
161 | * @var array |
||
162 | */ |
||
163 | private static $searchable_fields = [ |
||
164 | 'Title', |
||
165 | 'Domains.Domain', |
||
166 | 'IsPublic', |
||
167 | ]; |
||
168 | |||
169 | /** |
||
170 | * @var string |
||
171 | */ |
||
172 | private static $default_sort = '"Title" ASC'; |
||
173 | |||
174 | /** |
||
175 | * Set allowed themes |
||
176 | * |
||
177 | * @param array $themes - Numeric array of all themes which are allowed to be selected for all subsites. |
||
178 | */ |
||
179 | public static function set_allowed_themes($themes) |
||
180 | { |
||
181 | self::$allowed_themes = $themes; |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Gets the subsite currently set in the session. |
||
186 | * |
||
187 | * @uses ControllerSubsites->controllerAugmentInit() |
||
188 | * @return DataObject The current Subsite |
||
189 | */ |
||
190 | public static function currentSubsite() |
||
191 | { |
||
192 | return Subsite::get()->byID(SubsiteState::singleton()->getSubsiteId()); |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * This function gets the current subsite ID from the session. It used in the backend so Ajax requests |
||
197 | * use the correct subsite. The frontend handles subsites differently. It calls getSubsiteIDForDomain |
||
198 | * directly from ModelAsController::getNestedController. Only gets Subsite instances which have their |
||
199 | * {@link IsPublic} flag set to TRUE. |
||
200 | * |
||
201 | * You can simulate subsite access without creating virtual hosts by appending ?SubsiteID=<ID> to the request. |
||
202 | * |
||
203 | * @return int ID of the current subsite instance |
||
204 | * |
||
205 | * @deprecated 2.0..3.0 Use SubsiteState::singleton()->getSubsiteId() instead |
||
206 | */ |
||
207 | public static function currentSubsiteID() |
||
208 | { |
||
209 | Deprecation::notice('3.0', 'Use SubsiteState::singleton()->getSubsiteId() instead'); |
||
210 | return SubsiteState::singleton()->getSubsiteId(); |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Switch to another subsite through storing the subsite identifier in the current PHP session. |
||
215 | * Only takes effect when {@link SubsiteState::singleton()->getUseSessions()} is set to TRUE. |
||
216 | * |
||
217 | * @param int|Subsite $subsite Either the ID of the subsite, or the subsite object itself |
||
218 | */ |
||
219 | public static function changeSubsite($subsite) |
||
220 | { |
||
221 | // Session subsite change only meaningful if the session is active. |
||
222 | // Otherwise we risk setting it to wrong value, e.g. if we rely on currentSubsiteID. |
||
223 | if (!SubsiteState::singleton()->getUseSessions()) { |
||
224 | return; |
||
225 | } |
||
226 | |||
227 | if (is_object($subsite)) { |
||
228 | $subsiteID = $subsite->ID; |
||
229 | } else { |
||
230 | $subsiteID = $subsite; |
||
231 | } |
||
232 | |||
233 | SubsiteState::singleton()->setSubsiteId($subsiteID); |
||
234 | |||
235 | // Set locale |
||
236 | if (is_object($subsite) && $subsite->Language !== '') { |
||
237 | $locale = (new IntlLocales())->localeFromLang($subsite->Language); |
||
238 | if ($locale) { |
||
239 | i18n::set_locale($locale); |
||
240 | } |
||
241 | } |
||
242 | |||
243 | Permission::reset(); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * Get a matching subsite for the given host, or for the current HTTP_HOST. |
||
248 | * Supports "fuzzy" matching of domains by placing an asterisk at the start of end of the string, |
||
249 | * for example matching all subdomains on *.example.com with one subsite, |
||
250 | * and all subdomains on *.example.org on another. |
||
251 | * |
||
252 | * @param $host string The host to find the subsite for. If not specified, $_SERVER['HTTP_HOST'] is used. |
||
253 | * @param bool $checkPermissions |
||
254 | * @return int Subsite ID |
||
255 | */ |
||
256 | public static function getSubsiteIDForDomain($host = null, $checkPermissions = true) |
||
257 | { |
||
258 | if ($host == null && isset($_SERVER['HTTP_HOST'])) { |
||
259 | $host = $_SERVER['HTTP_HOST']; |
||
260 | } |
||
261 | |||
262 | $matchingDomains = null; |
||
263 | $cacheKey = null; |
||
264 | if ($host) { |
||
265 | if (!static::config()->get('strict_subdomain_matching')) { |
||
266 | $host = preg_replace('/^www\./', '', $host); |
||
267 | } |
||
268 | |||
269 | $currentUserId = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0; |
||
270 | $cacheKey = implode('_', [$host, $currentUserId, static::config()->get('check_is_public')]); |
||
271 | if (isset(self::$cache_subsite_for_domain[$cacheKey])) { |
||
272 | return self::$cache_subsite_for_domain[$cacheKey]; |
||
273 | } |
||
274 | |||
275 | $SQL_host = Convert::raw2sql($host); |
||
276 | |||
277 | $schema = DataObject::getSchema(); |
||
278 | |||
279 | /** @skipUpgrade */ |
||
280 | $domainTableName = $schema->tableName(SubsiteDomain::class); |
||
281 | if (!in_array($domainTableName, DB::table_list())) { |
||
282 | // Table hasn't been created yet. Might be a dev/build, skip. |
||
283 | return 0; |
||
284 | } |
||
285 | |||
286 | $subsiteTableName = $schema->tableName(__CLASS__); |
||
287 | /** @skipUpgrade */ |
||
288 | $matchingDomains = DataObject::get( |
||
289 | SubsiteDomain::class, |
||
290 | "'$SQL_host' LIKE replace(\"{$domainTableName}\".\"Domain\",'*','%')", |
||
291 | '"IsPrimary" DESC' |
||
292 | )->innerJoin( |
||
293 | $subsiteTableName, |
||
294 | '"' . $subsiteTableName . '"."ID" = "SubsiteDomain"."SubsiteID" AND "' |
||
295 | . $subsiteTableName . '"."IsPublic"=1' |
||
296 | ); |
||
297 | } |
||
298 | |||
299 | if ($matchingDomains && $matchingDomains->count()) { |
||
300 | $subsiteIDs = array_unique($matchingDomains->column('SubsiteID')); |
||
301 | $subsiteDomains = array_unique($matchingDomains->column('Domain')); |
||
302 | if (sizeof($subsiteIDs) > 1) { |
||
303 | throw new UnexpectedValueException(sprintf( |
||
304 | "Multiple subsites match on '%s': %s", |
||
305 | $host, |
||
306 | implode(',', $subsiteDomains) |
||
307 | )); |
||
308 | } |
||
309 | |||
310 | $subsiteID = $subsiteIDs[0]; |
||
311 | } else { |
||
312 | if ($default = DataObject::get_one(Subsite::class, '"DefaultSite" = 1')) { |
||
313 | // Check for a 'default' subsite |
||
314 | $subsiteID = $default->ID; |
||
315 | } else { |
||
316 | // Default subsite id = 0, the main site |
||
317 | $subsiteID = 0; |
||
318 | } |
||
319 | } |
||
320 | |||
321 | if ($cacheKey) { |
||
322 | self::$cache_subsite_for_domain[$cacheKey] = $subsiteID; |
||
323 | } |
||
324 | |||
325 | return $subsiteID; |
||
326 | } |
||
327 | |||
328 | /** |
||
329 | * |
||
330 | * @param string $className |
||
331 | * @param string $filter |
||
332 | * @param string $sort |
||
333 | * @param string $join |
||
334 | * @param string $limit |
||
335 | * @return DataList |
||
336 | */ |
||
337 | public static function get_from_all_subsites($className, $filter = '', $sort = '', $join = '', $limit = '') |
||
338 | { |
||
339 | $result = DataObject::get($className, $filter, $sort, $join, $limit); |
||
340 | $result = $result->setDataQueryParam('Subsite.filter', false); |
||
341 | return $result; |
||
342 | } |
||
343 | |||
344 | /** |
||
345 | * Disable the sub-site filtering; queries will select from all subsites |
||
346 | * @param bool $disabled |
||
347 | */ |
||
348 | public static function disable_subsite_filter($disabled = true) |
||
349 | { |
||
350 | self::$disable_subsite_filter = $disabled; |
||
351 | } |
||
352 | |||
353 | /** |
||
354 | * Flush caches on database reset |
||
355 | */ |
||
356 | public static function on_db_reset() |
||
357 | { |
||
358 | self::$cache_accessible_sites = []; |
||
359 | self::$cache_subsite_for_domain = []; |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Return all subsites, regardless of permissions (augmented with main site). |
||
364 | * |
||
365 | * @param bool $includeMainSite |
||
366 | * @param string $mainSiteTitle |
||
367 | * @return SS_List List of <a href='psi_element://Subsite'>Subsite</a> objects (DataList or ArrayList). |
||
368 | * objects (DataList or ArrayList). |
||
369 | */ |
||
370 | public static function all_sites($includeMainSite = true, $mainSiteTitle = 'Main site') |
||
371 | { |
||
372 | $subsites = Subsite::get(); |
||
373 | |||
374 | if ($includeMainSite) { |
||
375 | $subsites = $subsites->toArray(); |
||
376 | |||
377 | $mainSite = new Subsite(); |
||
378 | $mainSite->Title = $mainSiteTitle; |
||
379 | array_unshift($subsites, $mainSite); |
||
380 | |||
381 | $subsites = ArrayList::create($subsites); |
||
382 | } |
||
383 | |||
384 | return $subsites; |
||
385 | } |
||
386 | |||
387 | /* |
||
388 | * Returns an ArrayList of the subsites accessible to the current user. |
||
389 | * It's enough for any section to be accessible for the site to be included. |
||
390 | * |
||
391 | * @return ArrayList of {@link Subsite} instances. |
||
392 | */ |
||
393 | public static function all_accessible_sites($includeMainSite = true, $mainSiteTitle = 'Main site', $member = null) |
||
394 | { |
||
395 | // Rationalise member arguments |
||
396 | if (!$member) { |
||
397 | $member = Security::getCurrentUser(); |
||
398 | } |
||
399 | if (!$member) { |
||
400 | return ArrayList::create(); |
||
401 | } |
||
402 | if (!is_object($member)) { |
||
403 | $member = DataObject::get_by_id(Member::class, $member); |
||
404 | } |
||
405 | |||
406 | $subsites = ArrayList::create(); |
||
407 | |||
408 | // Collect subsites for all sections. |
||
409 | $menu = CMSMenu::get_viewable_menu_items(); |
||
410 | foreach ($menu as $candidate) { |
||
411 | if ($candidate->controller) { |
||
412 | $accessibleSites = singleton($candidate->controller)->sectionSites( |
||
413 | $includeMainSite, |
||
414 | $mainSiteTitle, |
||
415 | $member |
||
416 | ); |
||
417 | |||
418 | // Replace existing keys so no one site appears twice. |
||
419 | $subsites->merge($accessibleSites); |
||
420 | } |
||
421 | } |
||
422 | |||
423 | $subsites->removeDuplicates(); |
||
424 | |||
425 | return $subsites; |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * Return the subsites that the current user can access by given permission. |
||
430 | * Sites will only be included if they have a Title. |
||
431 | * |
||
432 | * @param $permCode array|string Either a single permission code or an array of permission codes. |
||
433 | * @param $includeMainSite bool If true, the main site will be included if appropriate. |
||
434 | * @param $mainSiteTitle string The label to give to the main site |
||
435 | * @param $member int|Member The member attempting to access the sites |
||
436 | * @return DataList|ArrayList of {@link Subsite} instances |
||
437 | */ |
||
438 | public static function accessible_sites( |
||
439 | $permCode, |
||
440 | $includeMainSite = true, |
||
441 | $mainSiteTitle = 'Main site', |
||
442 | $member = null |
||
443 | ) { |
||
444 | |||
445 | // Rationalise member arguments |
||
446 | if (!$member) { |
||
447 | $member = Member::currentUser(); |
||
448 | } |
||
449 | if (!$member) { |
||
450 | return new ArrayList(); |
||
451 | } |
||
452 | if (!is_object($member)) { |
||
453 | $member = DataObject::get_by_id(Member::class, $member); |
||
454 | } |
||
455 | |||
456 | // Rationalise permCode argument |
||
457 | if (is_array($permCode)) { |
||
458 | $SQL_codes = "'" . implode("', '", Convert::raw2sql($permCode)) . "'"; |
||
459 | } else { |
||
460 | $SQL_codes = "'" . Convert::raw2sql($permCode) . "'"; |
||
461 | } |
||
462 | |||
463 | // Cache handling |
||
464 | $cacheKey = $SQL_codes . '-' . $member->ID . '-' . $includeMainSite . '-' . $mainSiteTitle; |
||
465 | if (isset(self::$cache_accessible_sites[$cacheKey])) { |
||
466 | return self::$cache_accessible_sites[$cacheKey]; |
||
467 | } |
||
468 | |||
469 | /** @skipUpgrade */ |
||
470 | $subsites = DataList::create(Subsite::class) |
||
471 | ->where("\"Subsite\".\"Title\" != ''") |
||
472 | ->leftJoin('Group_Subsites', '"Group_Subsites"."SubsiteID" = "Subsite"."ID"') |
||
473 | ->innerJoin( |
||
474 | 'Group', |
||
475 | '"Group"."ID" = "Group_Subsites"."GroupID" OR "Group"."AccessAllSubsites" = 1' |
||
476 | ) |
||
477 | ->innerJoin( |
||
478 | 'Group_Members', |
||
479 | "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID" |
||
480 | ) |
||
481 | ->innerJoin( |
||
482 | 'Permission', |
||
483 | "\"Group\".\"ID\"=\"Permission\".\"GroupID\" |
||
484 | AND \"Permission\".\"Code\" |
||
485 | IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')" |
||
486 | ); |
||
487 | |||
488 | if (!$subsites) { |
||
489 | $subsites = new ArrayList(); |
||
490 | } |
||
491 | |||
492 | /** @var DataList $rolesSubsites */ |
||
493 | /** @skipUpgrade */ |
||
494 | $rolesSubsites = DataList::create(Subsite::class) |
||
495 | ->where("\"Subsite\".\"Title\" != ''") |
||
496 | ->leftJoin('Group_Subsites', '"Group_Subsites"."SubsiteID" = "Subsite"."ID"') |
||
497 | ->innerJoin( |
||
498 | 'Group', |
||
499 | '"Group"."ID" = "Group_Subsites"."GroupID" OR "Group"."AccessAllSubsites" = 1' |
||
500 | ) |
||
501 | ->innerJoin( |
||
502 | 'Group_Members', |
||
503 | "\"Group_Members\".\"GroupID\"=\"Group\".\"ID\" AND \"Group_Members\".\"MemberID\" = $member->ID" |
||
504 | ) |
||
505 | ->innerJoin('Group_Roles', '"Group_Roles"."GroupID"="Group"."ID"') |
||
506 | ->innerJoin('PermissionRole', '"Group_Roles"."PermissionRoleID"="PermissionRole"."ID"') |
||
507 | ->innerJoin( |
||
508 | 'PermissionRoleCode', |
||
509 | "\"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" |
||
510 | AND \"PermissionRoleCode\".\"Code\" |
||
511 | IN ($SQL_codes, 'CMS_ACCESS_LeftAndMain', 'ADMIN')" |
||
512 | ); |
||
513 | |||
514 | if (!$subsites && $rolesSubsites) { |
||
515 | return $rolesSubsites; |
||
516 | } |
||
517 | |||
518 | $subsites = new ArrayList($subsites->toArray()); |
||
519 | |||
520 | if ($rolesSubsites) { |
||
521 | foreach ($rolesSubsites as $subsite) { |
||
522 | if (!$subsites->find('ID', $subsite->ID)) { |
||
523 | $subsites->push($subsite); |
||
524 | } |
||
525 | } |
||
526 | } |
||
527 | |||
528 | if ($includeMainSite) { |
||
529 | if (!is_array($permCode)) { |
||
530 | $permCode = [$permCode]; |
||
531 | } |
||
532 | if (self::hasMainSitePermission($member, $permCode)) { |
||
533 | $subsites = $subsites->toArray(); |
||
534 | |||
535 | $mainSite = new Subsite(); |
||
536 | $mainSite->Title = $mainSiteTitle; |
||
537 | array_unshift($subsites, $mainSite); |
||
538 | $subsites = ArrayList::create($subsites); |
||
539 | } |
||
540 | } |
||
541 | |||
542 | self::$cache_accessible_sites[$cacheKey] = $subsites; |
||
543 | |||
544 | return $subsites; |
||
545 | } |
||
546 | |||
547 | /** |
||
548 | * Write a host->domain map to subsites/host-map.php |
||
549 | * |
||
550 | * This is used primarily when using subsites in conjunction with StaticPublisher |
||
551 | * |
||
552 | * @param string $file - filepath of the host map to be written |
||
553 | * @return void |
||
554 | */ |
||
555 | public static function writeHostMap($file = null) |
||
556 | { |
||
557 | if (!static::config()->get('write_hostmap')) { |
||
558 | return; |
||
559 | } |
||
560 | |||
561 | if (!$file) { |
||
562 | $subsitesPath = ModuleLoader::getModule('silverstripe/subsites')->getRelativePath(); |
||
563 | $file = Director::baseFolder() . $subsitesPath . '/host-map.php'; |
||
564 | } |
||
565 | $hostmap = []; |
||
566 | |||
567 | $subsites = DataObject::get(Subsite::class); |
||
568 | |||
569 | if ($subsites) { |
||
570 | foreach ($subsites as $subsite) { |
||
571 | $domains = $subsite->Domains(); |
||
572 | if ($domains) { |
||
573 | foreach ($domains as $domain) { |
||
574 | $domainStr = $domain->Domain; |
||
575 | if (!static::config()->get('strict_subdomain_matching')) { |
||
576 | $domainStr = preg_replace('/^www\./', '', $domainStr); |
||
577 | } |
||
578 | $hostmap[$domainStr] = $subsite->domain(); |
||
579 | } |
||
580 | } |
||
581 | if ($subsite->DefaultSite) { |
||
582 | $hostmap['default'] = $subsite->domain(); |
||
583 | } |
||
584 | } |
||
585 | } |
||
586 | |||
587 | $data = "<?php \n"; |
||
588 | $data .= "// Generated by Subsite::writeHostMap() on " . date('d/M/y') . "\n"; |
||
589 | $data .= '$subsiteHostmap = ' . var_export($hostmap, true) . ';'; |
||
590 | |||
591 | if (is_writable(dirname($file)) || is_writable($file)) { |
||
592 | file_put_contents($file, $data); |
||
593 | } |
||
594 | } |
||
595 | |||
596 | /** |
||
597 | * Checks if a member can be granted certain permissions, regardless of the subsite context. |
||
598 | * Similar logic to {@link Permission::checkMember()}, but only returns TRUE |
||
599 | * if the member is part of a group with the "AccessAllSubsites" flag set. |
||
600 | * If more than one permission is passed to the method, at least one of them must |
||
601 | * be granted for if to return TRUE. |
||
602 | * |
||
603 | * @todo Allow permission inheritance through group hierarchy. |
||
604 | * |
||
605 | * @param Member Member to check against. Defaults to currently logged in member |
||
606 | * @param array $permissionCodes |
||
607 | * @return bool |
||
608 | */ |
||
609 | public static function hasMainSitePermission($member = null, $permissionCodes = ['ADMIN']) |
||
610 | { |
||
611 | if (!is_array($permissionCodes)) { |
||
612 | user_error('Permissions must be passed to Subsite::hasMainSitePermission as an array', E_USER_ERROR); |
||
613 | } |
||
614 | |||
615 | if (!$member && $member !== false) { |
||
616 | $member = Security::getCurrentUser(); |
||
617 | } |
||
618 | |||
619 | if (!$member) { |
||
620 | return false; |
||
621 | } |
||
622 | |||
623 | if (!in_array('ADMIN', $permissionCodes)) { |
||
624 | $permissionCodes[] = 'ADMIN'; |
||
625 | } |
||
626 | |||
627 | $SQLa_perm = Convert::raw2sql($permissionCodes); |
||
628 | $SQL_perms = join("','", $SQLa_perm); |
||
629 | $memberID = (int)$member->ID; |
||
630 | |||
631 | // Count this user's groups which can access the main site |
||
632 | $groupCount = DB::query(" |
||
633 | SELECT COUNT(\"Permission\".\"ID\") |
||
634 | FROM \"Permission\" |
||
635 | INNER JOIN \"Group\" |
||
636 | ON \"Group\".\"ID\" = \"Permission\".\"GroupID\" AND \"Group\".\"AccessAllSubsites\" = 1 |
||
637 | INNER JOIN \"Group_Members\" |
||
638 | ON \"Group_Members\".\"GroupID\" = \"Permission\".\"GroupID\" |
||
639 | WHERE \"Permission\".\"Code\" |
||
640 | IN ('$SQL_perms') AND \"Group_Members\".\"MemberID\" = {$memberID} |
||
641 | ")->value(); |
||
642 | |||
643 | // Count this user's groups which have a role that can access the main site |
||
644 | $roleCount = DB::query(" |
||
645 | SELECT COUNT(\"PermissionRoleCode\".\"ID\") |
||
646 | FROM \"Group\" |
||
647 | INNER JOIN \"Group_Members\" ON \"Group_Members\".\"GroupID\" = \"Group\".\"ID\" |
||
648 | INNER JOIN \"Group_Roles\" ON \"Group_Roles\".\"GroupID\"=\"Group\".\"ID\" |
||
649 | INNER JOIN \"PermissionRole\" ON \"Group_Roles\".\"PermissionRoleID\"=\"PermissionRole\".\"ID\" |
||
650 | INNER JOIN \"PermissionRoleCode\" ON \"PermissionRole\".\"ID\"=\"PermissionRoleCode\".\"RoleID\" |
||
651 | WHERE \"PermissionRoleCode\".\"Code\" IN ('$SQL_perms') |
||
652 | AND \"Group\".\"AccessAllSubsites\" = 1 |
||
653 | AND \"Group_Members\".\"MemberID\" = {$memberID} |
||
654 | ")->value(); |
||
655 | |||
656 | // There has to be at least one that allows access. |
||
657 | return ($groupCount + $roleCount > 0); |
||
658 | } |
||
659 | |||
660 | /** |
||
661 | * @todo Possible security issue, don't grant edit permissions to everybody. |
||
662 | * @param bool $member |
||
663 | * @return bool |
||
664 | */ |
||
665 | public function canEdit($member = false) |
||
666 | { |
||
667 | $extended = $this->extendedCan(__FUNCTION__, $member); |
||
668 | if ($extended !== null) { |
||
669 | return $extended; |
||
670 | } |
||
671 | |||
672 | return true; |
||
673 | } |
||
674 | |||
675 | /** |
||
676 | * Show the configuration fields for each subsite |
||
677 | * |
||
678 | * @return FieldList |
||
679 | */ |
||
680 | public function getCMSFields() |
||
681 | { |
||
682 | $this->beforeUpdateCMSFields(function (FieldList $fields) { |
||
683 | if ($this->exists()) { |
||
684 | // Add a GridField for domains to a new tab if the subsite has already been created |
||
685 | $fields->addFieldsToTab('Root.Domains', [ |
||
686 | GridField::create( |
||
687 | 'Domains', |
||
688 | '', |
||
689 | $this->Domains(), |
||
690 | GridFieldConfig_RecordEditor::create(10) |
||
691 | ) |
||
692 | ]); |
||
693 | } |
||
694 | |||
695 | // Remove the default scaffolded blacklist field, we replace it with a checkbox set field |
||
696 | // in a wrapper further down. The RedirectURL field is currently not in use. |
||
697 | $fields->removeByName(['PageTypeBlacklist', 'RedirectURL']); |
||
698 | |||
699 | $fields->addFieldToTab('Root.Main', DropdownField::create( |
||
700 | 'Language', |
||
701 | $this->fieldLabel('Language'), |
||
702 | Injector::inst()->get(IntlLocales::class)->getLocales() |
||
703 | ), 'DefaultSite'); |
||
704 | |||
705 | $fields->addFieldsToTab('Root.Main', [ |
||
706 | ToggleCompositeField::create( |
||
707 | 'PageTypeBlacklistToggle', |
||
708 | _t(__CLASS__ . '.PageTypeBlacklistField', 'Disallow page types?'), |
||
709 | [ |
||
710 | CheckboxSetField::create('PageTypeBlacklist', '', $this->getPageTypeMap()) |
||
711 | ] |
||
712 | )->setHeadingLevel(4), |
||
713 | HiddenField::create('ID', '', $this->ID), |
||
714 | HiddenField::create('IsSubsite', '', 1) |
||
715 | ]); |
||
716 | |||
717 | // If there are any themes available, add the dropdown |
||
718 | $themes = $this->allowedThemes(); |
||
719 | if (!empty($themes)) { |
||
720 | $fields->addFieldToTab( |
||
721 | 'Root.Main', |
||
722 | DropdownField::create('Theme', $this->fieldLabel('Theme'), $this->allowedThemes(), $this->Theme) |
||
723 | ->setEmptyString(_t(__CLASS__ . '.ThemeFieldEmptyString', '-')), |
||
724 | 'PageTypeBlacklistToggle' |
||
725 | ); |
||
726 | } |
||
727 | |||
728 | // Targetted by the XHR PJAX JavaScript to reload the subsite list in the CMS |
||
729 | $fields->fieldByName('Root.Main')->addExtraClass('subsite-model'); |
||
730 | |||
731 | // We don't need the Groups many many tab |
||
732 | $fields->removeByName('Groups'); |
||
733 | |||
734 | // Rename the main tab to configuration |
||
735 | $fields->fieldByName('Root.Main')->setTitle(_t(__CLASS__ . '.ConfigurationTab', 'Configuration')); |
||
736 | }); |
||
737 | |||
738 | return parent::getCMSFields(); |
||
739 | } |
||
740 | |||
741 | /** |
||
742 | * Return a list of the different page types available to the CMS |
||
743 | * |
||
744 | * @return array |
||
745 | */ |
||
746 | public function getPageTypeMap() |
||
747 | { |
||
748 | $pageTypeMap = []; |
||
749 | |||
750 | $pageTypes = SiteTree::page_type_classes(); |
||
751 | foreach ($pageTypes as $pageType) { |
||
752 | $pageTypeMap[$pageType] = singleton($pageType)->i18n_singular_name(); |
||
753 | } |
||
754 | |||
755 | asort($pageTypeMap); |
||
756 | |||
757 | return $pageTypeMap; |
||
758 | } |
||
759 | |||
760 | /** |
||
761 | * |
||
762 | * @param boolean $includerelations |
||
763 | * @return array |
||
764 | */ |
||
765 | public function fieldLabels($includerelations = true) |
||
766 | { |
||
767 | $labels = parent::fieldLabels($includerelations); |
||
768 | $labels['Title'] = _t('Subsites.TitleFieldLabel', 'Subsite Name'); |
||
769 | $labels['RedirectURL'] = _t('Subsites.RedirectURLFieldLabel', 'Redirect URL'); |
||
770 | $labels['DefaultSite'] = _t('Subsites.DefaultSiteFieldLabel', 'Default site'); |
||
771 | $labels['Theme'] = _t('Subsites.ThemeFieldLabel', 'Theme'); |
||
772 | $labels['Language'] = _t('Subsites.LanguageFieldLabel', 'Language'); |
||
773 | $labels['IsPublic'] = _t('Subsites.IsPublicFieldLabel', 'Enable public access'); |
||
774 | $labels['PageTypeBlacklist'] = _t('Subsites.PageTypeBlacklistFieldLabel', 'Page Type Blacklist'); |
||
775 | $labels['Domains.Domain'] = _t('Subsites.DomainFieldLabel', 'Domain'); |
||
776 | $labels['PrimaryDomain'] = _t('Subsites.PrimaryDomainFieldLabel', 'Primary Domain'); |
||
777 | |||
778 | return $labels; |
||
779 | } |
||
780 | |||
781 | /** |
||
782 | * Return the themes that can be used with this subsite, as an array of themecode => description |
||
783 | * |
||
784 | * @return array |
||
785 | */ |
||
786 | public function allowedThemes() |
||
787 | { |
||
788 | if ($themes = self::$allowed_themes) { |
||
789 | return ArrayLib::valuekey($themes); |
||
790 | } |
||
791 | |||
792 | $themes = []; |
||
793 | if (is_dir(THEMES_PATH)) { |
||
794 | foreach (scandir(THEMES_PATH) as $theme) { |
||
795 | if ($theme[0] == '.') { |
||
796 | continue; |
||
797 | } |
||
798 | $theme = strtok($theme, '_'); |
||
799 | $themes[$theme] = $theme; |
||
800 | } |
||
801 | ksort($themes); |
||
802 | } |
||
803 | return $themes; |
||
804 | } |
||
805 | |||
806 | /** |
||
807 | * @return string Current locale of the subsite |
||
808 | */ |
||
809 | public function getLanguage() |
||
810 | { |
||
811 | if ($this->getField('Language')) { |
||
812 | return $this->getField('Language'); |
||
813 | } |
||
814 | |||
815 | return i18n::get_locale(); |
||
816 | } |
||
817 | |||
818 | /** |
||
819 | * |
||
820 | * @return \SilverStripe\ORM\ValidationResult |
||
821 | */ |
||
822 | public function validate() |
||
823 | { |
||
824 | $result = parent::validate(); |
||
825 | if (!$this->Title) { |
||
826 | $result->addError(_t(__CLASS__ . '.ValidateTitle', 'Please add a "Title"')); |
||
827 | } |
||
828 | return $result; |
||
829 | } |
||
830 | |||
831 | /** |
||
832 | * Whenever a Subsite is written, rewrite the hostmap |
||
833 | * |
||
834 | * @return void |
||
835 | */ |
||
836 | public function onAfterWrite() |
||
837 | { |
||
838 | Subsite::writeHostMap(); |
||
839 | parent::onAfterWrite(); |
||
840 | } |
||
841 | |||
842 | /** |
||
843 | * Return the primary domain of this site. Tries to "normalize" the domain name, |
||
844 | * by replacing potential wildcards. |
||
845 | * |
||
846 | * @return string The full domain name of this subsite (without protocol prefix) |
||
847 | */ |
||
848 | public function domain() |
||
849 | { |
||
850 | // Get best SubsiteDomain object |
||
851 | $domainObject = $this->getPrimarySubsiteDomain(); |
||
852 | if ($domainObject) { |
||
853 | return $domainObject->SubstitutedDomain; |
||
854 | } |
||
855 | |||
856 | // If there are no objects, default to the current hostname |
||
857 | return $_SERVER['HTTP_HOST']; |
||
858 | } |
||
859 | |||
860 | /** |
||
861 | * Finds the primary {@see SubsiteDomain} object for this subsite |
||
862 | * |
||
863 | * @return SubsiteDomain |
||
864 | */ |
||
865 | public function getPrimarySubsiteDomain() |
||
866 | { |
||
867 | return $this |
||
868 | ->Domains() |
||
869 | ->sort('"IsPrimary" DESC') |
||
870 | ->first(); |
||
871 | } |
||
872 | |||
873 | /** |
||
874 | * |
||
875 | * @return string - The full domain name of this subsite (without protocol prefix) |
||
876 | */ |
||
877 | public function getPrimaryDomain() |
||
878 | { |
||
879 | return $this->domain(); |
||
880 | } |
||
881 | |||
882 | /** |
||
883 | * Get the absolute URL for this subsite |
||
884 | * @return string |
||
885 | */ |
||
886 | public function absoluteBaseURL() |
||
887 | { |
||
888 | // Get best SubsiteDomain object |
||
889 | $domainObject = $this->getPrimarySubsiteDomain(); |
||
890 | if ($domainObject) { |
||
891 | return $domainObject->absoluteBaseURL(); |
||
892 | } |
||
893 | |||
894 | // Fall back to the current base url |
||
895 | return Director::absoluteBaseURL(); |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * Javascript admin action to duplicate this subsite |
||
900 | * |
||
901 | * @return string - javascript |
||
902 | */ |
||
903 | public function adminDuplicate() |
||
904 | { |
||
905 | $newItem = $this->duplicate(); |
||
906 | $message = _t( |
||
907 | __CLASS__ . '.CopyMessage', |
||
908 | 'Created a copy of {title}', |
||
909 | ['title' => Convert::raw2js($this->Title)] |
||
910 | ); |
||
911 | |||
912 | return <<<JS |
||
913 | statusMessage($message, 'good'); |
||
914 | $('Form_EditForm').loadURLFromServer('admin/subsites/show/$newItem->ID'); |
||
915 | JS; |
||
916 | } |
||
917 | |||
918 | /** |
||
919 | * Make this subsite the current one |
||
920 | */ |
||
921 | public function activate() |
||
922 | { |
||
923 | Subsite::changeSubsite($this); |
||
924 | } |
||
925 | |||
926 | /** |
||
927 | * |
||
928 | * @param array $permissionCodes |
||
929 | * @return DataList |
||
930 | */ |
||
931 | public function getMembersByPermission($permissionCodes = ['ADMIN']) |
||
932 | { |
||
933 | if (!is_array($permissionCodes)) { |
||
934 | user_error('Permissions must be passed to Subsite::getMembersByPermission as an array', E_USER_ERROR); |
||
935 | } |
||
936 | $SQL_permissionCodes = Convert::raw2sql($permissionCodes); |
||
937 | |||
938 | $SQL_permissionCodes = join("','", $SQL_permissionCodes); |
||
939 | |||
940 | return DataObject::get( |
||
941 | Member::class, |
||
942 | "\"Group\".\"SubsiteID\" = $this->ID AND \"Permission\".\"Code\" IN ('$SQL_permissionCodes')", |
||
943 | '', |
||
944 | 'LEFT JOIN "Group_Members" ON "Member"."ID" = "Group_Members"."MemberID" |
||
945 | LEFT JOIN "Group" ON "Group"."ID" = "Group_Members"."GroupID" |
||
946 | LEFT JOIN "Permission" ON "Permission"."GroupID" = "Group"."ID"' |
||
947 | ); |
||
948 | } |
||
949 | |||
950 | /** |
||
951 | * Duplicate this subsite |
||
952 | * @param bool $doWrite |
||
953 | * @param string $manyMany |
||
954 | * @return DataObject |
||
955 | */ |
||
956 | public function duplicate($doWrite = true, $manyMany = 'many_many') |
||
957 | { |
||
958 | $duplicate = parent::duplicate($doWrite); |
||
959 | |||
960 | $oldSubsiteID = SubsiteState::singleton()->getSubsiteId(); |
||
961 | self::changeSubsite($this->ID); |
||
962 | |||
963 | /* |
||
964 | * Copy data from this object to the given subsite. Does this using an iterative depth-first search. |
||
965 | * This will make sure that the new parents on the new subsite are correct, and there are no funny |
||
966 | * issues with having to check whether or not the new parents have been added to the site tree |
||
967 | * when a page, etc, is duplicated |
||
968 | */ |
||
969 | $stack = [[0, 0]]; |
||
970 | while (count($stack) > 0) { |
||
971 | list($sourceParentID, $destParentID) = array_pop($stack); |
||
972 | $children = Versioned::get_by_stage('Page', 'Live', "\"ParentID\" = $sourceParentID", ''); |
||
973 | |||
974 | if ($children) { |
||
975 | foreach ($children as $child) { |
||
976 | self::changeSubsite($duplicate->ID); //Change to destination subsite |
||
977 | |||
978 | $childClone = $child->duplicateToSubsite($duplicate, false); |
||
979 | $childClone->ParentID = $destParentID; |
||
980 | $childClone->writeToStage('Stage'); |
||
981 | $childClone->copyVersionToStage('Stage', 'Live'); |
||
982 | |||
983 | self::changeSubsite($this->ID); //Change Back to this subsite |
||
984 | |||
985 | array_push($stack, [$child->ID, $childClone->ID]); |
||
986 | } |
||
987 | } |
||
988 | } |
||
989 | |||
990 | self::changeSubsite($oldSubsiteID); |
||
991 | |||
992 | return $duplicate; |
||
993 | } |
||
994 | } |
||
995 |
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.