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\Admin\AdminRootController; |
||
6 | use SilverStripe\Admin\CMSMenu; |
||
7 | use SilverStripe\Admin\LeftAndMainExtension; |
||
8 | use SilverStripe\CMS\Model\SiteTree; |
||
9 | use SilverStripe\CMS\Controllers\CMSPageEditController; |
||
10 | use SilverStripe\Control\Controller; |
||
11 | use SilverStripe\Core\Config\Config; |
||
12 | use SilverStripe\Core\Convert; |
||
13 | use SilverStripe\Core\Manifest\ModuleLoader; |
||
14 | use SilverStripe\Forms\HiddenField; |
||
15 | use SilverStripe\ORM\ArrayList; |
||
16 | use SilverStripe\ORM\DataObject; |
||
17 | use SilverStripe\Security\Member; |
||
18 | use SilverStripe\Security\Permission; |
||
19 | use SilverStripe\Security\Security; |
||
20 | use SilverStripe\Subsites\Controller\SubsiteXHRController; |
||
21 | use SilverStripe\Subsites\Model\Subsite; |
||
22 | use SilverStripe\Subsites\State\SubsiteState; |
||
23 | use SilverStripe\View\ArrayData; |
||
24 | use SilverStripe\View\Requirements; |
||
25 | |||
26 | /** |
||
27 | * Decorator designed to add subsites support to LeftAndMain |
||
28 | * |
||
29 | * @package subsites |
||
30 | */ |
||
31 | class LeftAndMainSubsites extends LeftAndMainExtension |
||
32 | { |
||
33 | private static $allowed_actions = ['CopyToSubsite']; |
||
0 ignored issues
–
show
Comprehensibility
introduced
by
Loading history...
|
|||
34 | |||
35 | /** |
||
36 | * Normally SubsiteID=0 on a DataObject means it is only accessible from the special "main site". |
||
37 | * However in some situations SubsiteID=0 will be understood as a "globally accessible" object in which |
||
38 | * case this property is set to true (i.e. in AssetAdmin). |
||
39 | */ |
||
40 | private static $treats_subsite_0_as_global = false; |
||
41 | |||
42 | public function init() |
||
43 | { |
||
44 | $module = ModuleLoader::getModule('silverstripe/subsites'); |
||
45 | |||
46 | Requirements::css($module->getRelativeResourcePath('css/LeftAndMain_Subsites.css')); |
||
47 | Requirements::javascript($module->getRelativeResourcePath('javascript/LeftAndMain_Subsites.js')); |
||
48 | Requirements::javascript($module->getRelativeResourcePath('javascript/VirtualPage_Subsites.js')); |
||
49 | } |
||
50 | |||
51 | /** |
||
52 | * Set the title of the CMS tree |
||
53 | */ |
||
54 | public function getCMSTreeTitle() |
||
55 | { |
||
56 | $subsite = Subsite::currentSubsite(); |
||
57 | return $subsite ? Convert::raw2xml($subsite->Title) : _t('LeftAndMain.SITECONTENTLEFT'); |
||
58 | } |
||
59 | |||
60 | public function updatePageOptions(&$fields) |
||
61 | { |
||
62 | $fields->push(HiddenField::create('SubsiteID', 'SubsiteID', SubsiteState::singleton()->getSubsiteId())); |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Find all subsites accessible for current user on this controller. |
||
67 | * |
||
68 | * @param bool $includeMainSite |
||
69 | * @param string $mainSiteTitle |
||
70 | * @param null $member |
||
71 | * @return ArrayList of <a href='psi_element://Subsite'>Subsite</a> instances. |
||
72 | * instances. |
||
73 | */ |
||
74 | public function sectionSites($includeMainSite = true, $mainSiteTitle = 'Main site', $member = null) |
||
75 | { |
||
76 | if ($mainSiteTitle == 'Main site') { |
||
77 | $mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site'); |
||
78 | } |
||
79 | |||
80 | // Rationalise member arguments |
||
81 | if (!$member) { |
||
82 | $member = Security::getCurrentUser(); |
||
83 | } |
||
84 | if (!$member) { |
||
85 | return ArrayList::create(); |
||
86 | } |
||
87 | if (!is_object($member)) { |
||
88 | $member = DataObject::get_by_id(Member::class, $member); |
||
89 | } |
||
90 | |||
91 | // Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires |
||
92 | // us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView. |
||
93 | $codes = []; |
||
94 | $extraCodes = Config::inst()->get(get_class($this->owner), 'required_permission_codes'); |
||
95 | if ($extraCodes !== false) { |
||
96 | if ($extraCodes) { |
||
97 | $codes = array_merge($codes, (array)$extraCodes); |
||
98 | } else { |
||
99 | $codes[] = sprintf('CMS_ACCESS_%s', get_class($this->owner)); |
||
100 | } |
||
101 | } else { |
||
102 | // Check overriden - all subsites accessible. |
||
103 | return Subsite::all_sites(); |
||
104 | } |
||
105 | |||
106 | // Find subsites satisfying all permissions for the Member. |
||
107 | $codesPerSite = []; |
||
108 | $sitesArray = []; |
||
109 | foreach ($codes as $code) { |
||
110 | $sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member); |
||
111 | foreach ($sites as $site) { |
||
112 | // Build the structure for checking how many codes match. |
||
113 | $codesPerSite[$site->ID][$code] = true; |
||
114 | |||
115 | // Retain Subsite objects for later. |
||
116 | $sitesArray[$site->ID] = $site; |
||
117 | } |
||
118 | } |
||
119 | |||
120 | // Find sites that satisfy all codes conjuncitvely. |
||
121 | $accessibleSites = new ArrayList(); |
||
122 | foreach ($codesPerSite as $siteID => $siteCodes) { |
||
123 | if (count($siteCodes) == count($codes)) { |
||
124 | $accessibleSites->push($sitesArray[$siteID]); |
||
125 | } |
||
126 | } |
||
127 | |||
128 | return $accessibleSites; |
||
129 | } |
||
130 | |||
131 | /* |
||
132 | * Returns a list of the subsites accessible to the current user. |
||
133 | * It's enough for any section to be accessible for the section to be included. |
||
134 | */ |
||
135 | public function Subsites() |
||
136 | { |
||
137 | return Subsite::all_accessible_sites(); |
||
138 | } |
||
139 | |||
140 | /* |
||
141 | * Generates a list of subsites with the data needed to |
||
142 | * produce a dropdown site switcher |
||
143 | * @return ArrayList |
||
144 | */ |
||
145 | |||
146 | public function ListSubsites() |
||
147 | { |
||
148 | $list = $this->Subsites(); |
||
149 | $currentSubsiteID = SubsiteState::singleton()->getSubsiteId(); |
||
150 | |||
151 | if ($list == null || $list->count() == 1 && $list->first()->DefaultSite == true) { |
||
152 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type documented by SilverStripe\Subsites\Ex...nSubsites::ListSubsites of type SilverStripe\ORM\ArrayList .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function
Loading history...
|
|||
153 | } |
||
154 | |||
155 | $module = ModuleLoader::getModule('silverstripe/subsites'); |
||
156 | Requirements::javascript($module->getRelativeResourcePath('javascript/LeftAndMain_Subsites.js')); |
||
157 | |||
158 | $output = ArrayList::create(); |
||
159 | |||
160 | foreach ($list as $subsite) { |
||
161 | $currentState = $subsite->ID == $currentSubsiteID ? 'selected' : ''; |
||
162 | |||
163 | $output->push(ArrayData::create([ |
||
164 | 'CurrentState' => $currentState, |
||
165 | 'ID' => $subsite->ID, |
||
166 | 'Title' => Convert::raw2xml($subsite->Title) |
||
167 | ])); |
||
168 | } |
||
169 | |||
170 | return $output; |
||
171 | } |
||
172 | |||
173 | public function alternateMenuDisplayCheck($controllerName) |
||
174 | { |
||
175 | if (!class_exists($controllerName)) { |
||
176 | return false; |
||
177 | } |
||
178 | |||
179 | // Don't display SubsiteXHRController |
||
180 | if (singleton($controllerName) instanceof SubsiteXHRController) { |
||
181 | return false; |
||
182 | } |
||
183 | |||
184 | // Check subsite support. |
||
185 | if (SubsiteState::singleton()->getSubsiteId() == 0) { |
||
0 ignored issues
–
show
|
|||
186 | // Main site always supports everything. |
||
187 | return true; |
||
188 | } |
||
189 | |||
190 | // It's not necessary to check access permissions here. Framework calls canView on the controller, |
||
191 | // which in turn uses the Permission API which is augmented by our GroupSubsites. |
||
192 | $controller = singleton($controllerName); |
||
193 | return $controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu(); |
||
194 | } |
||
195 | |||
196 | public function CanAddSubsites() |
||
197 | { |
||
198 | return Permission::check('ADMIN', 'any', null, 'all'); |
||
0 ignored issues
–
show
'all' is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
Loading history...
|
|||
199 | } |
||
200 | |||
201 | /** |
||
202 | * Helper for testing if the subsite should be adjusted. |
||
203 | * @param string $adminClass |
||
204 | * @param int $recordSubsiteID |
||
205 | * @param int $currentSubsiteID |
||
206 | * @return bool |
||
207 | */ |
||
208 | public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID) |
||
209 | { |
||
210 | if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID == 0) { |
||
211 | return false; |
||
212 | } |
||
213 | if ($recordSubsiteID != $currentSubsiteID) { |
||
214 | return true; |
||
215 | } |
||
216 | return false; |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Check if the current controller is accessible for this user on this subsite. |
||
221 | */ |
||
222 | public function canAccess() |
||
223 | { |
||
224 | // Admin can access everything, no point in checking. |
||
225 | $member = Security::getCurrentUser(); |
||
226 | if ($member |
||
227 | && (Permission::checkMember($member, 'ADMIN') // 'Full administrative rights' |
||
228 | || Permission::checkMember($member, 'CMS_ACCESS_LeftAndMain') // 'Access to all CMS sections' |
||
229 | ) |
||
230 | ) { |
||
231 | return true; |
||
232 | } |
||
233 | |||
234 | // Check if we have access to current section on the current subsite. |
||
235 | $accessibleSites = $this->owner->sectionSites(true, 'Main site', $member); |
||
236 | return $accessibleSites->count() && $accessibleSites->find('ID', SubsiteState::singleton()->getSubsiteId()); |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | * Prevent accessing disallowed resources. This happens after onBeforeInit has executed, |
||
241 | * so all redirections should've already taken place. |
||
242 | */ |
||
243 | public function alternateAccessCheck() |
||
244 | { |
||
245 | return $this->owner->canAccess(); |
||
246 | } |
||
247 | |||
248 | /** |
||
249 | * Redirect the user to something accessible if the current section/subsite is forbidden. |
||
250 | * |
||
251 | * This is done via onBeforeInit as it needs to be done before the LeftAndMain::init has a |
||
252 | * chance to forbids access via alternateAccessCheck. |
||
253 | * |
||
254 | * If we need to change the subsite we force the redirection to /admin/ so the frontend is |
||
255 | * fully re-synchronised with the internal session. This is better than risking some panels |
||
256 | * showing data from another subsite. |
||
257 | */ |
||
258 | public function onBeforeInit() |
||
259 | { |
||
260 | $request = Controller::curr()->getRequest(); |
||
261 | $session = $request->getSession(); |
||
262 | |||
263 | $state = SubsiteState::singleton(); |
||
264 | |||
265 | // FIRST, check if we need to change subsites due to the URL. |
||
266 | |||
267 | // Catch forced subsite changes that need to cause CMS reloads. |
||
268 | if ($request->getVar('SubsiteID') !== null) { |
||
269 | // Clear current page when subsite changes (or is set for the first time) |
||
270 | if ($state->getSubsiteIdWasChanged()) { |
||
271 | // sessionNamespace() is protected - see for info |
||
272 | $override = $this->owner->config()->get('session_namespace'); |
||
273 | $sessionNamespace = $override ? $override : get_class($this->owner); |
||
274 | $session->clear($sessionNamespace . '.currentPage'); |
||
275 | } |
||
276 | |||
277 | // Subsite ID has already been set to the state via InitStateMiddleware. If the user cannot view |
||
278 | // the current page, or we're in the context of editing a specific page, redirect to the admin landing |
||
279 | // section to prevent a loop of re-loading the original subsite for the current page. |
||
280 | if (!$this->owner->canView() || Controller::curr() instanceof CMSPageEditController) { |
||
281 | return $this->owner->redirect(AdminRootController::config()->get('url_base') . '/'); |
||
282 | } |
||
283 | |||
284 | // Redirect to clear the current page, retaining the current URL parameters |
||
285 | return $this->owner->redirect( |
||
286 | Controller::join_links($this->owner->Link(), ...array_values($this->owner->getURLParams())) |
||
287 | ); |
||
288 | } |
||
289 | |||
290 | // Automatically redirect the session to appropriate subsite when requesting a record. |
||
291 | // This is needed to properly initialise the session in situations where someone opens the CMS via a link. |
||
292 | $record = $this->owner->currentPage(); |
||
293 | if ($record |
||
294 | && isset($record->SubsiteID, $this->owner->urlParams['ID']) |
||
295 | && is_numeric($record->SubsiteID) |
||
296 | && $this->shouldChangeSubsite( |
||
297 | get_class($this->owner), |
||
298 | $record->SubsiteID, |
||
299 | SubsiteState::singleton()->getSubsiteId() |
||
300 | ) |
||
301 | ) { |
||
302 | // Update current subsite |
||
303 | $canViewElsewhere = SubsiteState::singleton()->withState(function ($newState) use ($record) { |
||
304 | $newState->setSubsiteId($record->SubsiteID); |
||
305 | |||
306 | if ($this->owner->canView(Security::getCurrentUser())) { |
||
307 | return true; |
||
308 | } |
||
309 | return false; |
||
310 | }); |
||
311 | |||
312 | if ($canViewElsewhere) { |
||
313 | // Redirect to clear the current page |
||
314 | return $this->owner->redirect( |
||
315 | Controller::join_links($this->owner->Link('show'), $record->ID, '?SubsiteID=' . $record->SubsiteID) |
||
316 | ); |
||
317 | } |
||
318 | // Redirect to the default CMS section |
||
319 | return $this->owner->redirect(AdminRootController::config()->get('url_base') . '/'); |
||
320 | } |
||
321 | |||
322 | // SECOND, check if we need to change subsites due to lack of permissions. |
||
323 | |||
324 | if (!$this->owner->canAccess()) { |
||
325 | $member = Security::getCurrentUser(); |
||
326 | |||
327 | // Current section is not accessible, try at least to stick to the same subsite. |
||
328 | $menu = CMSMenu::get_menu_items(); |
||
329 | foreach ($menu as $candidate) { |
||
330 | if ($candidate->controller && $candidate->controller != get_class($this->owner)) { |
||
331 | $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member); |
||
332 | if ($accessibleSites->count() |
||
333 | && $accessibleSites->find('ID', SubsiteState::singleton()->getSubsiteId()) |
||
334 | ) { |
||
335 | // Section is accessible, redirect there. |
||
336 | return $this->owner->redirect(singleton($candidate->controller)->Link()); |
||
337 | } |
||
338 | } |
||
339 | } |
||
340 | |||
341 | // If no section is available, look for other accessible subsites. |
||
342 | foreach ($menu as $candidate) { |
||
343 | if ($candidate->controller) { |
||
344 | $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member); |
||
345 | if ($accessibleSites->count()) { |
||
346 | Subsite::changeSubsite($accessibleSites->First()->ID); |
||
347 | return $this->owner->redirect(singleton($candidate->controller)->Link()); |
||
348 | } |
||
349 | } |
||
350 | } |
||
351 | |||
352 | // We have not found any accessible section or subsite. User should be denied access. |
||
353 | return Security::permissionFailure($this->owner); |
||
354 | } |
||
355 | |||
356 | // Current site is accessible. Allow through. |
||
357 | return; |
||
358 | } |
||
359 | |||
360 | public function augmentNewSiteTreeItem(&$item) |
||
361 | { |
||
362 | $request = Controller::curr()->getRequest(); |
||
363 | $item->SubsiteID = $request->postVar('SubsiteID') ?: SubsiteState::singleton()->getSubsiteId(); |
||
364 | } |
||
365 | |||
366 | public function onAfterSave($record) |
||
367 | { |
||
368 | if ($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) { |
||
369 | $this->owner->response->addHeader( |
||
370 | 'X-Status', |
||
371 | rawurlencode(_t('LeftAndMainSubsites.Saved', 'Saved, please update related pages.')) |
||
372 | ); |
||
373 | } |
||
374 | } |
||
375 | |||
376 | /** |
||
377 | * @param array $data |
||
378 | * @param Form $form |
||
379 | */ |
||
380 | public function copytosubsite($data, $form) |
||
0 ignored issues
–
show
|
|||
381 | { |
||
382 | $page = DataObject::get_by_id(SiteTree::class, $data['ID']); |
||
383 | $subsite = DataObject::get_by_id(Subsite::class, $data['CopyToSubsiteID']); |
||
384 | $includeChildren = (isset($data['CopyToSubsiteWithChildren'])) ? $data['CopyToSubsiteWithChildren'] : false; |
||
385 | |||
386 | $newPage = $page->duplicateToSubsite($subsite->ID, $includeChildren); |
||
387 | $response = $this->owner->getResponse(); |
||
388 | $response->addHeader('X-Reload', true); |
||
389 | |||
390 | return $this->owner->redirect(Controller::join_links( |
||
391 | $this->owner->Link('show'), |
||
392 | $newPage->ID |
||
393 | )); |
||
394 | } |
||
395 | } |
||
396 |