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