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