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