Completed
Push — master ( 0ebf95...33622c )
by Damian
12s
created

LeftAndMainSubsites::onBeforeInit()   D

Complexity

Conditions 21
Paths 22

Size

Total Lines 100
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 46
c 0
b 0
f 0
nc 22
nop 0
dl 0
loc 100
rs 4.6955

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Forms\HiddenField;
14
use SilverStripe\ORM\ArrayList;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\Security\Member;
17
use SilverStripe\Security\Permission;
18
use SilverStripe\Security\Security;
19
use SilverStripe\Subsites\Controller\SubsiteXHRController;
20
use SilverStripe\Subsites\Model\Subsite;
21
use SilverStripe\Subsites\State\SubsiteState;
22
use SilverStripe\View\ArrayData;
23
use SilverStripe\View\Requirements;
24
25
/**
26
 * Decorator designed to add subsites support to LeftAndMain
27
 *
28
 * @package subsites
29
 */
30
class LeftAndMainSubsites extends LeftAndMainExtension
31
{
32
    private static $allowed_actions = ['CopyToSubsite'];
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
33
34
    /**
35
     * Normally SubsiteID=0 on a DataObject means it is only accessible from the special "main site".
36
     * However in some situations SubsiteID=0 will be understood as a "globally accessible" object in which
37
     * case this property is set to true (i.e. in AssetAdmin).
38
     */
39
    private static $treats_subsite_0_as_global = false;
0 ignored issues
show
introduced by
The private property $treats_subsite_0_as_global is not used, and could be removed.
Loading history...
40
41
    public function init()
42
    {
43
        Requirements::css('silverstripe/subsites:css/LeftAndMain_Subsites.css');
44
        Requirements::javascript('silverstripe/subsites:javascript/LeftAndMain_Subsites.js');
45
        Requirements::javascript('silverstripe/subsites:javascript/VirtualPage_Subsites.js');
46
    }
47
48
    /**
49
     * Set the title of the CMS tree
50
     */
51
    public function getCMSTreeTitle()
52
    {
53
        $subsite = Subsite::currentSubsite();
54
        return $subsite ? Convert::raw2xml($subsite->Title) : _t(__CLASS__.'.SITECONTENTLEFT', 'Site Content');
55
    }
56
57
    public function updatePageOptions(&$fields)
58
    {
59
        $fields->push(HiddenField::create('SubsiteID', 'SubsiteID', SubsiteState::singleton()->getSubsiteId()));
0 ignored issues
show
Bug introduced by
'SubsiteID' of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\ViewableData::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

59
        $fields->push(HiddenField::create(/** @scrutinizer ignore-type */ 'SubsiteID', 'SubsiteID', SubsiteState::singleton()->getSubsiteId()));
Loading history...
60
    }
61
62
    /**
63
     * Find all subsites accessible for current user on this controller.
64
     *
65
     * @param bool $includeMainSite
66
     * @param string $mainSiteTitle
67
     * @param null $member
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $member is correct as it would always require null to be passed?
Loading history...
68
     * @return ArrayList of <a href='psi_element://Subsite'>Subsite</a> instances.
69
     * instances.
70
     */
71
    public function sectionSites($includeMainSite = true, $mainSiteTitle = 'Main site', $member = null)
72
    {
73
        if ($mainSiteTitle == 'Main site') {
74
            $mainSiteTitle = _t('Subsites.MainSiteTitle', 'Main site');
75
        }
76
77
        // Rationalise member arguments
78
        if (!$member) {
79
            $member = Security::getCurrentUser();
80
        }
81
        if (!$member) {
82
            return ArrayList::create();
83
        }
84
        if (!is_object($member)) {
85
            $member = DataObject::get_by_id(Member::class, $member);
86
        }
87
88
        // Collect permissions - honour the LeftAndMain::required_permission_codes, current model requires
89
        // us to check if the user satisfies ALL permissions. Code partly copied from LeftAndMain::canView.
90
        $codes = [];
91
        $extraCodes = Config::inst()->get(get_class($this->owner), 'required_permission_codes');
92
        if ($extraCodes !== false) {
93
            if ($extraCodes) {
94
                $codes = array_merge($codes, (array)$extraCodes);
95
            } else {
96
                $codes[] = sprintf('CMS_ACCESS_%s', get_class($this->owner));
97
            }
98
        } else {
99
            // Check overriden - all subsites accessible.
100
            return Subsite::all_sites();
101
        }
102
103
        // Find subsites satisfying all permissions for the Member.
104
        $codesPerSite = [];
105
        $sitesArray = [];
106
        foreach ($codes as $code) {
107
            $sites = Subsite::accessible_sites($code, $includeMainSite, $mainSiteTitle, $member);
108
            foreach ($sites as $site) {
109
                // Build the structure for checking how many codes match.
110
                $codesPerSite[$site->ID][$code] = true;
111
112
                // Retain Subsite objects for later.
113
                $sitesArray[$site->ID] = $site;
114
            }
115
        }
116
117
        // Find sites that satisfy all codes conjuncitvely.
118
        $accessibleSites = new ArrayList();
119
        foreach ($codesPerSite as $siteID => $siteCodes) {
120
            if (count($siteCodes) == count($codes)) {
121
                $accessibleSites->push($sitesArray[$siteID]);
122
            }
123
        }
124
125
        return $accessibleSites;
126
    }
127
128
    /*
129
     * Returns a list of the subsites accessible to the current user.
130
     * It's enough for any section to be accessible for the section to be included.
131
     */
132
    public function Subsites()
133
    {
134
        return Subsite::all_accessible_sites();
135
    }
136
137
    /*
138
     * Generates a list of subsites with the data needed to
139
     * produce a dropdown site switcher
140
     * @return ArrayList
141
     */
142
143
    public function ListSubsites()
144
    {
145
        $list = $this->Subsites();
146
        $currentSubsiteID = SubsiteState::singleton()->getSubsiteId();
147
148
        if ($list == null || $list->count() == 1 && $list->first()->DefaultSite == true) {
149
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type SilverStripe\ORM\ArrayList.
Loading history...
150
        }
151
152
        Requirements::javascript('silverstripe/subsites:javascript/LeftAndMain_Subsites.js');
153
154
        $output = ArrayList::create();
155
156
        foreach ($list as $subsite) {
157
            $currentState = $subsite->ID == $currentSubsiteID ? 'selected' : '';
158
159
            $output->push(ArrayData::create([
160
                'CurrentState' => $currentState,
161
                'ID' => $subsite->ID,
162
                'Title' => Convert::raw2xml($subsite->Title)
163
            ]));
164
        }
165
166
        return $output;
167
    }
168
169
    public function alternateMenuDisplayCheck($controllerName)
170
    {
171
        if (!class_exists($controllerName)) {
172
            return false;
173
        }
174
175
        // Don't display SubsiteXHRController
176
        if (singleton($controllerName) instanceof SubsiteXHRController) {
177
            return false;
178
        }
179
180
        // Check subsite support.
181
        if (SubsiteState::singleton()->getSubsiteId() == 0) {
182
            // Main site always supports everything.
183
            return true;
184
        }
185
186
        // It's not necessary to check access permissions here. Framework calls canView on the controller,
187
        // which in turn uses the Permission API which is augmented by our GroupSubsites.
188
        $controller = singleton($controllerName);
189
        return $controller->hasMethod('subsiteCMSShowInMenu') && $controller->subsiteCMSShowInMenu();
190
    }
191
192
    public function CanAddSubsites()
193
    {
194
        return Permission::check('ADMIN', 'any', null, 'all');
0 ignored issues
show
Bug introduced by
'all' of type string is incompatible with the type boolean expected by parameter $strict of SilverStripe\Security\Permission::check(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

194
        return Permission::check('ADMIN', 'any', null, /** @scrutinizer ignore-type */ 'all');
Loading history...
195
    }
196
197
    /**
198
     * Helper for testing if the subsite should be adjusted.
199
     * @param string $adminClass
200
     * @param int $recordSubsiteID
201
     * @param int $currentSubsiteID
202
     * @return bool
203
     */
204
    public function shouldChangeSubsite($adminClass, $recordSubsiteID, $currentSubsiteID)
205
    {
206
        if (Config::inst()->get($adminClass, 'treats_subsite_0_as_global') && $recordSubsiteID == 0) {
207
            return false;
208
        }
209
        if ($recordSubsiteID != $currentSubsiteID) {
210
            return true;
211
        }
212
        return false;
213
    }
214
215
    /**
216
     * Check if the current controller is accessible for this user on this subsite.
217
     */
218
    public function canAccess()
219
    {
220
        // Admin can access everything, no point in checking.
221
        $member = Security::getCurrentUser();
222
        if ($member
223
            && (Permission::checkMember($member, 'ADMIN') // 'Full administrative rights'
224
                || Permission::checkMember($member, 'CMS_ACCESS_LeftAndMain') // 'Access to all CMS sections'
225
            )
226
        ) {
227
            return true;
228
        }
229
230
        // Check if we have access to current section on the current subsite.
231
        $accessibleSites = $this->owner->sectionSites(true, 'Main site', $member);
232
        return $accessibleSites->count() && $accessibleSites->find('ID', SubsiteState::singleton()->getSubsiteId());
233
    }
234
235
    /**
236
     * Prevent accessing disallowed resources. This happens after onBeforeInit has executed,
237
     * so all redirections should've already taken place.
238
     */
239
    public function alternateAccessCheck()
240
    {
241
        return $this->owner->canAccess();
242
    }
243
244
    /**
245
     * Redirect the user to something accessible if the current section/subsite is forbidden.
246
     *
247
     * This is done via onBeforeInit as it needs to be done before the LeftAndMain::init has a
248
     * chance to forbids access via alternateAccessCheck.
249
     *
250
     * If we need to change the subsite we force the redirection to /admin/ so the frontend is
251
     * fully re-synchronised with the internal session. This is better than risking some panels
252
     * showing data from another subsite.
253
     */
254
    public function onBeforeInit()
255
    {
256
        $request = Controller::curr()->getRequest();
257
        $session = $request->getSession();
258
259
        $state = SubsiteState::singleton();
260
261
        // FIRST, check if we need to change subsites due to the URL.
262
263
        // Catch forced subsite changes that need to cause CMS reloads.
264
        if ($request->getVar('SubsiteID') !== null) {
265
            // Clear current page when subsite changes (or is set for the first time)
266
            if ($state->getSubsiteIdWasChanged()) {
267
                // sessionNamespace() is protected - see for info
268
                $override = $this->owner->config()->get('session_namespace');
269
                $sessionNamespace = $override ? $override : get_class($this->owner);
270
                $session->clear($sessionNamespace . '.currentPage');
271
            }
272
273
            // Subsite ID has already been set to the state via InitStateMiddleware. If the user cannot view
274
            // the current page, or we're in the context of editing a specific page, redirect to the admin landing
275
            // section to prevent a loop of re-loading the original subsite for the current page.
276
            if (!$this->owner->canView() || Controller::curr() instanceof CMSPageEditController) {
277
                return $this->owner->redirect(AdminRootController::config()->get('url_base') . '/');
278
            }
279
280
            // Redirect to clear the current page, retaining the current URL parameters
281
            return $this->owner->redirect(
282
                Controller::join_links($this->owner->Link(), ...array_values($this->owner->getURLParams()))
283
            );
284
        }
285
286
        // Automatically redirect the session to appropriate subsite when requesting a record.
287
        // This is needed to properly initialise the session in situations where someone opens the CMS via a link.
288
        $record = $this->owner->currentPage();
289
        if ($record
290
            && isset($record->SubsiteID, $this->owner->urlParams['ID'])
291
            && is_numeric($record->SubsiteID)
292
            && $this->shouldChangeSubsite(
293
                get_class($this->owner),
294
                $record->SubsiteID,
295
                SubsiteState::singleton()->getSubsiteId()
296
            )
297
        ) {
298
            // Update current subsite
299
            $canViewElsewhere = SubsiteState::singleton()->withState(function ($newState) use ($record) {
300
                $newState->setSubsiteId($record->SubsiteID);
301
302
                if ($this->owner->canView(Security::getCurrentUser())) {
303
                    return true;
304
                }
305
                return false;
306
            });
307
308
            if ($canViewElsewhere) {
309
                // Redirect to clear the current page
310
                return $this->owner->redirect(
311
                    Controller::join_links($this->owner->Link('show'), $record->ID, '?SubsiteID=' . $record->SubsiteID)
312
                );
313
            }
314
            // Redirect to the default CMS section
315
            return $this->owner->redirect(AdminRootController::config()->get('url_base') . '/');
316
        }
317
318
        // SECOND, check if we need to change subsites due to lack of permissions.
319
320
        if (!$this->owner->canAccess()) {
321
            $member = Security::getCurrentUser();
322
323
            // Current section is not accessible, try at least to stick to the same subsite.
324
            $menu = CMSMenu::get_menu_items();
325
            foreach ($menu as $candidate) {
326
                if ($candidate->controller && $candidate->controller != get_class($this->owner)) {
327
                    $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
328
                    if ($accessibleSites->count()
329
                        && $accessibleSites->find('ID', SubsiteState::singleton()->getSubsiteId())
330
                    ) {
331
                        // Section is accessible, redirect there.
332
                        return $this->owner->redirect(singleton($candidate->controller)->Link());
333
                    }
334
                }
335
            }
336
337
            // If no section is available, look for other accessible subsites.
338
            foreach ($menu as $candidate) {
339
                if ($candidate->controller) {
340
                    $accessibleSites = singleton($candidate->controller)->sectionSites(true, 'Main site', $member);
341
                    if ($accessibleSites->count()) {
342
                        Subsite::changeSubsite($accessibleSites->First()->ID);
343
                        return $this->owner->redirect(singleton($candidate->controller)->Link());
344
                    }
345
                }
346
            }
347
348
            // We have not found any accessible section or subsite. User should be denied access.
349
            return Security::permissionFailure($this->owner);
350
        }
351
352
        // Current site is accessible. Allow through.
353
        return;
354
    }
355
356
    public function augmentNewSiteTreeItem(&$item)
357
    {
358
        $request = Controller::curr()->getRequest();
359
        $item->SubsiteID = $request->postVar('SubsiteID') ?: SubsiteState::singleton()->getSubsiteId();
360
    }
361
362
    public function onAfterSave($record)
363
    {
364
        if ($record->hasMethod('NormalRelated') && ($record->NormalRelated() || $record->ReverseRelated())) {
365
            $this->owner->response->addHeader(
366
                'X-Status',
367
                rawurlencode(_t(__CLASS__ . '.Saved', 'Saved, please update related pages.'))
368
            );
369
        }
370
    }
371
372
    /**
373
     * @param array $data
374
     * @param Form $form
0 ignored issues
show
Bug introduced by
The type SilverStripe\Subsites\Extensions\Form was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
375
     */
376
    public function copytosubsite($data, $form)
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

376
    public function copytosubsite($data, /** @scrutinizer ignore-unused */ $form)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
377
    {
378
        $page = DataObject::get_by_id(SiteTree::class, $data['ID']);
379
        $subsite = DataObject::get_by_id(Subsite::class, $data['CopyToSubsiteID']);
380
        $includeChildren = (isset($data['CopyToSubsiteWithChildren'])) ? $data['CopyToSubsiteWithChildren'] : false;
381
382
        $newPage = $page->duplicateToSubsite($subsite->ID, $includeChildren);
383
        $response = $this->owner->getResponse();
384
        $response->addHeader('X-Reload', true);
385
386
        return $this->owner->redirect(Controller::join_links(
387
            $this->owner->Link('show'),
388
            $newPage->ID
389
        ));
390
    }
391
}
392