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\Extensions; |
||
4 | |||
5 | use SilverStripe\CMS\Model\SiteTree; |
||
6 | use SilverStripe\Control\Controller; |
||
7 | use SilverStripe\Control\Director; |
||
8 | use SilverStripe\Control\HTTP; |
||
9 | use SilverStripe\Core\Config\Config; |
||
10 | use SilverStripe\Core\Convert; |
||
11 | use SilverStripe\Forms\CheckboxField; |
||
12 | use SilverStripe\Forms\DropdownField; |
||
13 | use SilverStripe\Forms\FieldList; |
||
14 | use SilverStripe\Forms\FormAction; |
||
15 | use SilverStripe\Forms\ToggleCompositeField; |
||
16 | use SilverStripe\ORM\DataExtension; |
||
17 | use SilverStripe\ORM\DataObject; |
||
18 | use SilverStripe\ORM\DataQuery; |
||
19 | use SilverStripe\ORM\Queries\SQLSelect; |
||
20 | use SilverStripe\Security\Member; |
||
21 | use SilverStripe\SiteConfig\SiteConfig; |
||
22 | use SilverStripe\Subsites\Model\Subsite; |
||
23 | use SilverStripe\View\SSViewer; |
||
24 | |||
25 | /** |
||
26 | * Extension for the SiteTree object to add subsites support |
||
27 | */ |
||
28 | class SiteTreeSubsites extends DataExtension |
||
29 | { |
||
30 | private static $has_one = [ |
||
31 | 'Subsite' => Subsite::class, // The subsite that this page belongs to |
||
32 | ]; |
||
33 | |||
34 | private static $many_many = [ |
||
35 | 'CrossSubsiteLinkTracking' => SiteTree::class // Stored separately, as the logic for URL rewriting is different |
||
36 | ]; |
||
37 | |||
38 | private static $many_many_extraFields = [ |
||
39 | 'CrossSubsiteLinkTracking' => ['FieldName' => 'Varchar'] |
||
40 | ]; |
||
41 | |||
42 | public function isMainSite() |
||
43 | { |
||
44 | return $this->owner->SubsiteID == 0; |
||
45 | } |
||
46 | |||
47 | /** |
||
48 | * Update any requests to limit the results to the current site |
||
49 | * @param SQLSelect $query |
||
50 | * @param DataQuery $dataQuery |
||
51 | */ |
||
52 | public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) |
||
53 | { |
||
54 | if (Subsite::$disable_subsite_filter) { |
||
55 | return; |
||
56 | } |
||
57 | if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) { |
||
58 | return; |
||
59 | } |
||
60 | |||
61 | // If you're querying by ID, ignore the sub-site - this is a bit ugly... |
||
62 | // if(!$query->where || (strpos($query->where[0], ".\"ID\" = ") === false && strpos($query->where[0], ".`ID` = ") === false && strpos($query->where[0], ".ID = ") === false && strpos($query->where[0], "ID = ") !== 0)) { |
||
63 | if ($query->filtersOnID()) { |
||
64 | return; |
||
65 | } |
||
66 | |||
67 | if (Subsite::$force_subsite) { |
||
68 | $subsiteID = Subsite::$force_subsite; |
||
69 | } else { |
||
70 | /*if($context = DataObject::context_obj()) $subsiteID = (int)$context->SubsiteID; |
||
0 ignored issues
–
show
|
|||
71 | else */ |
||
72 | $subsiteID = (int)Subsite::currentSubsiteID(); |
||
73 | } |
||
74 | |||
75 | // The foreach is an ugly way of getting the first key :-) |
||
76 | foreach ($query->getFrom() as $tableName => $info) { |
||
77 | // The tableName should be SiteTree or SiteTree_Live... |
||
78 | $siteTreeTableName = SiteTree::getSchema()->tableName(SiteTree::class); |
||
79 | if (strpos($tableName, $siteTreeTableName) === false) { |
||
80 | break; |
||
81 | } |
||
82 | $query->addWhere("\"$tableName\".\"SubsiteID\" IN ($subsiteID)"); |
||
83 | break; |
||
84 | } |
||
85 | } |
||
86 | |||
87 | public function onBeforeWrite() |
||
88 | { |
||
89 | View Code Duplication | if (!$this->owner->ID && !$this->owner->SubsiteID) { |
|
90 | $this->owner->SubsiteID = Subsite::currentSubsiteID(); |
||
91 | } |
||
92 | |||
93 | parent::onBeforeWrite(); |
||
94 | } |
||
95 | |||
96 | public function updateCMSFields(FieldList $fields) |
||
97 | { |
||
98 | $subsites = Subsite::accessible_sites('CMS_ACCESS_CMSMain'); |
||
99 | $subsitesMap = []; |
||
100 | if ($subsites && $subsites->count()) { |
||
101 | $subsitesToMap = $subsites->exclude('ID', $this->owner->SubsiteID); |
||
102 | $subsitesMap = $subsitesToMap->map('ID', 'Title'); |
||
103 | } |
||
104 | |||
105 | // Master page edit field (only allowed from default subsite to avoid inconsistent relationships) |
||
106 | $isDefaultSubsite = $this->owner->SubsiteID == 0 || $this->owner->Subsite()->DefaultSite; |
||
107 | |||
108 | if ($isDefaultSubsite && $subsitesMap) { |
||
109 | $fields->addFieldToTab( |
||
110 | 'Root.Main', |
||
111 | ToggleCompositeField::create( |
||
112 | 'SubsiteOperations', |
||
113 | _t('SiteTreeSubsites.SubsiteOperations', 'Subsite Operations'), |
||
114 | [ |
||
115 | new DropdownField('CopyToSubsiteID', _t( |
||
116 | 'SiteTreeSubsites.CopyToSubsite', |
||
117 | 'Copy page to subsite' |
||
118 | ), $subsitesMap), |
||
119 | new CheckboxField( |
||
120 | 'CopyToSubsiteWithChildren', |
||
121 | _t('SiteTreeSubsites.CopyToSubsiteWithChildren', 'Include children pages?') |
||
122 | ), |
||
123 | $copyAction = new FormAction( |
||
124 | 'copytosubsite', |
||
125 | _t('SiteTreeSubsites.CopyAction', 'Copy') |
||
126 | ) |
||
127 | ] |
||
128 | )->setHeadingLevel(4) |
||
129 | ); |
||
130 | |||
131 | |||
132 | // $copyAction->includeDefaultJS(false); |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
75% 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...
|
|||
133 | } |
||
134 | |||
135 | // replace readonly link prefix |
||
136 | $subsite = $this->owner->Subsite(); |
||
137 | $nested_urls_enabled = Config::inst()->get(SiteTree::class, 'nested_urls'); |
||
138 | if ($subsite && $subsite->exists()) { |
||
139 | // Use baseurl from domain |
||
140 | $baseLink = $subsite->absoluteBaseURL(); |
||
141 | |||
142 | // Add parent page if enabled |
||
143 | if ($nested_urls_enabled && $this->owner->ParentID) { |
||
144 | $baseLink = Controller::join_links( |
||
145 | $baseLink, |
||
146 | $this->owner->Parent()->RelativeLink(true) |
||
147 | ); |
||
148 | } |
||
149 | |||
150 | $urlsegment = $fields->dataFieldByName('URLSegment'); |
||
151 | $urlsegment->setURLPrefix($baseLink); |
||
152 | } |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * Does the basic duplication, but doesn't write anything |
||
157 | * this means we can subclass this easier and do more complex |
||
158 | * relation duplication. |
||
159 | */ |
||
160 | public function duplicateToSubsitePrep($subsiteID) |
||
161 | { |
||
162 | if (is_object($subsiteID)) { |
||
163 | $subsiteID = $subsiteID->ID; |
||
164 | } |
||
165 | |||
166 | $oldSubsite = Subsite::currentSubsiteID(); |
||
167 | if ($subsiteID) { |
||
168 | Subsite::changeSubsite($subsiteID); |
||
169 | } else { |
||
170 | $subsiteID = $oldSubsite; |
||
171 | } |
||
172 | // doesn't write as we need to reset the SubsiteID, ParentID etc |
||
173 | $clone = $this->owner->duplicate(false); |
||
174 | $clone->CheckedPublicationDifferences = $clone->AddedToStage = true; |
||
175 | $subsiteID = ($subsiteID ? $subsiteID : $oldSubsite); |
||
176 | $clone->SubsiteID = $subsiteID; |
||
177 | // We have no idea what the parentID should be, so as a workaround use the url-segment and subsite ID |
||
178 | if ($this->owner->Parent()) { |
||
179 | $parentSeg = $this->owner->Parent()->URLSegment; |
||
180 | $newParentPage = Page::get()->filter('URLSegment', $parentSeg)->first(); |
||
181 | if ($newParentPage) { |
||
182 | $clone->ParentID = $newParentPage->ID; |
||
183 | } else { |
||
184 | // reset it to the top level, so the user can decide where to put it |
||
185 | $clone->ParentID = 0; |
||
186 | } |
||
187 | } |
||
188 | // MasterPageID is here for legacy purposes, to satisfy the subsites_relatedpages module |
||
189 | $clone->MasterPageID = $this->owner->ID; |
||
190 | return $clone; |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * Create a duplicate of this page and save it to another subsite |
||
195 | * @param $subsiteID int|Subsite The Subsite to copy to, or its ID |
||
196 | */ |
||
197 | public function duplicateToSubsite($subsiteID = null) |
||
198 | { |
||
199 | $clone = $this->owner->duplicateToSubsitePrep($subsiteID); |
||
200 | $clone->invokeWithExtensions('onBeforeDuplicateToSubsite', $this->owner); |
||
201 | $clone->write(); |
||
202 | $clone->duplicateSubsiteRelations($this->owner); |
||
203 | // new extension hooks which happens after write, |
||
204 | // onAfterDuplicate isn't reliable due to |
||
205 | // https://github.com/silverstripe/silverstripe-cms/issues/1253 |
||
206 | $clone->invokeWithExtensions('onAfterDuplicateToSubsite', $this->owner); |
||
207 | return $clone; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Duplicate relations using a static property to define |
||
212 | * which ones we want to duplicate |
||
213 | * |
||
214 | * It may be that some relations are not diostinct to sub site so can stay |
||
215 | * whereas others may need to be duplicated |
||
216 | * |
||
217 | */ |
||
218 | public function duplicateSubsiteRelations($originalPage) |
||
219 | { |
||
220 | $thisClass = $originalPage->ClassName; |
||
221 | $relations = Config::inst()->get($thisClass, 'duplicate_to_subsite_relations'); |
||
222 | |||
223 | if ($relations && !empty($relations)) { |
||
224 | foreach ($relations as $relation) { |
||
225 | $items = $originalPage->$relation(); |
||
226 | foreach ($items as $item) { |
||
227 | $duplicateItem = $item->duplicate(false); |
||
228 | $duplicateItem->{$thisClass.'ID'} = $this->owner->ID; |
||
229 | $duplicateItem->write(); |
||
230 | } |
||
231 | } |
||
232 | } |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * @return SiteConfig |
||
237 | */ |
||
238 | public function alternateSiteConfig() |
||
239 | { |
||
240 | if (!$this->owner->SubsiteID) { |
||
241 | return false; |
||
242 | } |
||
243 | $sc = DataObject::get_one(SiteConfig::class, '"SubsiteID" = ' . $this->owner->SubsiteID); |
||
244 | if (!$sc) { |
||
245 | $sc = new SiteConfig(); |
||
246 | $sc->SubsiteID = $this->owner->SubsiteID; |
||
247 | $sc->Title = _t('Subsite.SiteConfigTitle', 'Your Site Name'); |
||
248 | $sc->Tagline = _t('Subsite.SiteConfigSubtitle', 'Your tagline here'); |
||
249 | $sc->write(); |
||
250 | } |
||
251 | return $sc; |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Only allow editing of a page if the member satisfies one of the following conditions: |
||
256 | * - Is in a group which has access to the subsite this page belongs to |
||
257 | * - Is in a group with edit permissions on the "main site" |
||
258 | * |
||
259 | * @param null $member |
||
260 | * @return bool |
||
261 | */ |
||
262 | public function canEdit($member = null) |
||
263 | { |
||
264 | if (!$member) { |
||
265 | $member = Member::currentUser(); |
||
266 | } |
||
267 | |||
268 | // Find the sites that this user has access to |
||
269 | $goodSites = Subsite::accessible_sites('CMS_ACCESS_CMSMain', true, 'all', $member)->column('ID'); |
||
270 | |||
271 | if (!is_null($this->owner->SubsiteID)) { |
||
272 | $subsiteID = $this->owner->SubsiteID; |
||
273 | } else { |
||
274 | // The relationships might not be available during the record creation when using a GridField. |
||
275 | // In this case the related objects will have empty fields, and SubsiteID will not be available. |
||
276 | // |
||
277 | // We do the second best: fetch the likely SubsiteID from the session. The drawback is this might |
||
278 | // make it possible to force relations to point to other (forbidden) subsites. |
||
279 | $subsiteID = Subsite::currentSubsiteID(); |
||
280 | } |
||
281 | |||
282 | // Return true if they have access to this object's site |
||
283 | if (!(in_array(0, $goodSites) || in_array($subsiteID, $goodSites))) { |
||
284 | return false; |
||
285 | } |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * @param null $member |
||
290 | * @return bool |
||
291 | */ |
||
292 | View Code Duplication | public function canDelete($member = null) |
|
293 | { |
||
294 | if (!$member && $member !== false) { |
||
295 | $member = Member::currentUser(); |
||
296 | } |
||
297 | |||
298 | return $this->canEdit($member); |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * @param null $member |
||
303 | * @return bool |
||
304 | */ |
||
305 | View Code Duplication | public function canAddChildren($member = null) |
|
306 | { |
||
307 | if (!$member && $member !== false) { |
||
308 | $member = Member::currentUser(); |
||
309 | } |
||
310 | |||
311 | return $this->canEdit($member); |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * @param null $member |
||
316 | * @return bool |
||
317 | */ |
||
318 | View Code Duplication | public function canPublish($member = null) |
|
319 | { |
||
320 | if (!$member && $member !== false) { |
||
321 | $member = Member::currentUser(); |
||
322 | } |
||
323 | |||
324 | return $this->canEdit($member); |
||
325 | } |
||
326 | |||
327 | /** |
||
328 | * Called by ContentController::init(); |
||
329 | * @param $controller |
||
330 | */ |
||
331 | View Code Duplication | public static function contentcontrollerInit($controller) |
|
332 | { |
||
333 | $subsite = Subsite::currentSubsite(); |
||
334 | |||
335 | if ($subsite && $subsite->Theme) { |
||
336 | SSViewer::set_themes(array_merge([$subsite->Theme], SSViewer::get_themes())); |
||
337 | } |
||
338 | } |
||
339 | |||
340 | public function alternateAbsoluteLink() |
||
341 | { |
||
342 | // Generate the existing absolute URL and replace the domain with the subsite domain. |
||
343 | // This helps deal with Link() returning an absolute URL. |
||
344 | $url = Director::absoluteURL($this->owner->Link()); |
||
345 | if ($this->owner->SubsiteID) { |
||
346 | $url = preg_replace('/\/\/[^\/]+\//', '//' . $this->owner->Subsite()->domain() . '/', $url); |
||
347 | } |
||
348 | return $url; |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * Use the CMS domain for iframed CMS previews to prevent single-origin violations |
||
353 | * and SSL cert problems. |
||
354 | * @param null $action |
||
355 | * @return string |
||
356 | */ |
||
357 | public function alternatePreviewLink($action = null) |
||
358 | { |
||
359 | $url = Director::absoluteURL($this->owner->Link()); |
||
360 | if ($this->owner->SubsiteID) { |
||
361 | $url = HTTP::setGetVar('SubsiteID', $this->owner->SubsiteID, $url); |
||
362 | } |
||
363 | return $url; |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * Inject the subsite ID into the content so it can be used by frontend scripts. |
||
368 | * @param $tags |
||
369 | * @return string |
||
370 | */ |
||
371 | public function MetaTags(&$tags) |
||
372 | { |
||
373 | if ($this->owner->SubsiteID) { |
||
374 | $tags .= '<meta name="x-subsite-id" content="' . $this->owner->SubsiteID . "\" />\n"; |
||
375 | } |
||
376 | |||
377 | return $tags; |
||
378 | } |
||
379 | |||
380 | public function augmentSyncLinkTracking() |
||
381 | { |
||
382 | // Set LinkTracking appropriately |
||
383 | $links = HTTP::getLinksIn($this->owner->Content); |
||
384 | $linkedPages = []; |
||
385 | |||
386 | if ($links) { |
||
387 | foreach ($links as $link) { |
||
388 | if (substr($link, 0, strlen('http://')) == 'http://') { |
||
389 | $withoutHttp = substr($link, strlen('http://')); |
||
390 | if (strpos($withoutHttp, '/') && strpos($withoutHttp, '/') < strlen($withoutHttp)) { |
||
391 | $domain = substr($withoutHttp, 0, strpos($withoutHttp, '/')); |
||
392 | $rest = substr($withoutHttp, strpos($withoutHttp, '/') + 1); |
||
393 | |||
394 | $subsiteID = Subsite::getSubsiteIDForDomain($domain); |
||
395 | if ($subsiteID == 0) { |
||
396 | continue; |
||
397 | } // We have no idea what the domain for the main site is, so cant track links to it |
||
398 | |||
399 | $origDisableSubsiteFilter = Subsite::$disable_subsite_filter; |
||
400 | Subsite::disable_subsite_filter(true); |
||
401 | $candidatePage = DataObject::get_one( |
||
402 | SiteTree::class, |
||
403 | "\"URLSegment\" = '" . Convert::raw2sql(urldecode($rest)) . "' AND \"SubsiteID\" = " . $subsiteID, |
||
404 | false |
||
405 | ); |
||
406 | Subsite::disable_subsite_filter($origDisableSubsiteFilter); |
||
407 | |||
408 | if ($candidatePage) { |
||
409 | $linkedPages[] = $candidatePage->ID; |
||
410 | } else { |
||
411 | $this->owner->HasBrokenLink = true; |
||
412 | } |
||
413 | } |
||
414 | } |
||
415 | } |
||
416 | } |
||
417 | |||
418 | $this->owner->CrossSubsiteLinkTracking()->setByIDList($linkedPages); |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * Ensure that valid url segments are checked within the correct subsite of the owner object, |
||
423 | * even if the current subsiteID is set to some other subsite. |
||
424 | * |
||
425 | * @return null|bool Either true or false, or null to not influence result |
||
426 | */ |
||
427 | public function augmentValidURLSegment() |
||
428 | { |
||
429 | // If this page is being filtered in the current subsite, then no custom validation query is required. |
||
430 | $subsite = Subsite::$force_subsite ?: Subsite::currentSubsiteID(); |
||
431 | if (empty($this->owner->SubsiteID) || $subsite == $this->owner->SubsiteID) { |
||
432 | return null; |
||
433 | } |
||
434 | |||
435 | // Backup forced subsite |
||
436 | $prevForceSubsite = Subsite::$force_subsite; |
||
437 | Subsite::$force_subsite = $this->owner->SubsiteID; |
||
438 | |||
439 | // Repeat validation in the correct subsite |
||
440 | $isValid = $this->owner->validURLSegment(); |
||
441 | |||
442 | // Restore |
||
443 | Subsite::$force_subsite = $prevForceSubsite; |
||
444 | |||
445 | return (bool)$isValid; |
||
446 | } |
||
447 | |||
448 | /** |
||
449 | * Return a piece of text to keep DataObject cache keys appropriately specific |
||
450 | */ |
||
451 | public function cacheKeyComponent() |
||
452 | { |
||
453 | return 'subsite-' . Subsite::currentSubsiteID(); |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * @param Member |
||
458 | * @return boolean|null |
||
459 | */ |
||
460 | public function canCreate($member = null) |
||
461 | { |
||
462 | // Typically called on a singleton, so we're not using the Subsite() relation |
||
463 | $subsite = Subsite::currentSubsite(); |
||
464 | if ($subsite && $subsite->exists() && $subsite->PageTypeBlacklist) { |
||
465 | $blacklisted = explode(',', $subsite->PageTypeBlacklist); |
||
466 | // All subclasses need to be listed explicitly |
||
467 | if (in_array($this->owner->class, $blacklisted)) { |
||
468 | return false; |
||
469 | } |
||
470 | } |
||
471 | } |
||
472 | } |
||
473 |
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.