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 Werner M. Krauß
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 Werner M. Krauß
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 Robbie Averill
'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 Werner M. Krauß
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 Werner M. Krauß
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 Werner M. Krauß
'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 Robbie Averill
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 Damian Mooyman
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