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

GroupSubsites::augmentSQL()   C

Complexity

Conditions 14
Paths 22

Size

Total Lines 47
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 29
c 0
b 0
f 0
nc 22
nop 2
dl 0
loc 47
rs 5.0622

How to fix   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\Control\Cookie;
6
use SilverStripe\Core\Convert;
7
use SilverStripe\Forms\CheckboxSetField;
8
use SilverStripe\Forms\FieldList;
9
use SilverStripe\Forms\OptionsetField;
10
use SilverStripe\Forms\ReadonlyField;
11
use SilverStripe\ORM\DataExtension;
12
use SilverStripe\ORM\DataQuery;
13
use SilverStripe\ORM\DB;
14
use SilverStripe\ORM\Queries\SQLSelect;
15
use SilverStripe\Security\Group;
16
use SilverStripe\Security\PermissionProvider;
17
use SilverStripe\Subsites\Model\Subsite;
18
use SilverStripe\Subsites\State\SubsiteState;
19
20
/**
21
 * Extension for the Group object to add subsites support
22
 *
23
 * @package subsites
24
 */
25
class GroupSubsites extends DataExtension implements PermissionProvider
26
{
27
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
28
        'AccessAllSubsites' => 'Boolean'
29
    ];
30
31
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
32
        'Subsites' => Subsite::class
33
    ];
34
35
    private static $defaults = [
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
36
        'AccessAllSubsites' => true
37
    ];
38
39
    /**
40
     * Migrations for GroupSubsites data.
41
     */
42
    public function requireDefaultRecords()
43
    {
44
        if (!$this->owner) {
45
            return;
46
        }
47
        // Migration for Group.SubsiteID data from when Groups only had a single subsite
48
        $ownerClass = get_class($this->owner);
49
        $schema = $ownerClass::getSchema();
50
        $groupFields = DB::field_list($schema->tableName(Group::class));
51
52
        // Detection of SubsiteID field is the trigger for old-style-subsiteID migration
53
        if (isset($groupFields['SubsiteID'])) {
54
            // Migrate subsite-specific data
55
            DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID")
56
				SELECT "ID", "SubsiteID" FROM "Group" WHERE "SubsiteID" > 0');
57
58
            // Migrate global-access data
59
            DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0');
60
61
            // Move the field out of the way so that this migration doesn't get executed again
62
            DB::get_schema()->renameField(Group::class, 'SubsiteID', '_obsolete_SubsiteID');
63
64
            // No subsite access on anything means that we've just installed the subsites module.
65
            // Make all previous groups global-access groups
66
        } else {
67
            if (!DB::query('SELECT "Group"."ID" FROM "Group"
68
			LEFT JOIN "Group_Subsites" ON "Group_Subsites"."GroupID" = "Group"."ID" AND "Group_Subsites"."SubsiteID" > 0
69
			WHERE "AccessAllSubsites" = 1
70
			OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()
71
            ) {
72
                DB::query('UPDATE "Group" SET "AccessAllSubsites" = 1');
73
            }
74
        }
75
    }
76
77
    public function updateCMSFields(FieldList $fields)
78
    {
79
        if ($this->owner->canEdit()) {
80
            // i18n tab
81
            $fields->findOrMakeTab('Root.Subsites', _t(__CLASS__ . '.SECURITYTABTITLE', 'Subsites'));
82
83
            $subsites = Subsite::accessible_sites(['ADMIN', 'SECURITY_SUBSITE_GROUP'], true);
84
            $subsiteMap = $subsites->map();
85
86
            // Prevent XSS injection
87
            $subsiteMap = Convert::raw2xml($subsiteMap->toArray());
88
89
            // Interface is different if you have the rights to modify subsite group values on
90
            // all subsites
91
            if (isset($subsiteMap[0])) {
92
                $fields->addFieldToTab('Root.Subsites', new OptionsetField(
93
                    'AccessAllSubsites',
94
                    _t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
95
                    [
96
                        1 => _t(__CLASS__ . '.ACCESSALL', 'All subsites'),
97
                        0 => _t(__CLASS__ . '.ACCESSONLY', 'Only these subsites'),
98
                    ]
99
                ));
100
101
                unset($subsiteMap[0]);
102
                $fields->addFieldToTab('Root.Subsites', new CheckboxSetField(
103
                    'Subsites',
104
                    '',
105
                    $subsiteMap
106
                ));
107
            } else {
108
                if (sizeof($subsiteMap) <= 1) {
0 ignored issues
show
Bug introduced by
The call to sizeof() has too few arguments starting with mode. ( Ignorable by Annotation )

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

108
                if (/** @scrutinizer ignore-call */ sizeof($subsiteMap) <= 1) {

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
109
                    $fields->addFieldToTab('Root.Subsites', new ReadonlyField(
110
                        'SubsitesHuman',
111
                        _t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
112
                        reset($subsiteMap)
113
                    ));
114
                } else {
115
                    $fields->addFieldToTab('Root.Subsites', new CheckboxSetField(
116
                        'Subsites',
117
                        _t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
118
                        $subsiteMap
119
                    ));
120
                }
121
            }
122
        }
123
    }
124
125
    /**
126
     * If this group belongs to a subsite, append the subsites title to the group title to make it easy to
127
     * distinguish in the tree-view of the security admin interface.
128
     *
129
     * @param string $title
130
     */
131
    public function updateTreeTitle(&$title)
132
    {
133
        if ($this->owner->AccessAllSubsites) {
134
            $title = _t(__CLASS__ . '.GlobalGroup', 'global group');
135
            $title = htmlspecialchars($this->owner->Title, ENT_QUOTES) . ' <i>(' . $title . ')</i>';
136
        } else {
137
            $subsites = Convert::raw2xml(implode(', ', $this->owner->Subsites()->column('Title')));
138
            $title = htmlspecialchars($this->owner->Title) . " <i>($subsites)</i>";
139
        }
140
    }
141
142
    /**
143
     * Update any requests to limit the results to the current site
144
     * @param SQLSelect $query
145
     * @param DataQuery|null $dataQuery
146
     */
147
    public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
148
    {
149
        if (Subsite::$disable_subsite_filter) {
150
            return;
151
        }
152
        if (Cookie::get('noSubsiteFilter') == 'true') {
153
            return;
154
        }
155
156
        // If you're querying by ID, ignore the sub-site - this is a bit ugly...
157
        if (!$query->filtersOnID()) {
158
            $subsiteID = SubsiteState::singleton()->getSubsiteId();
159
            if ($subsiteID === null) {
160
                return;
161
            }
162
163
            // Don't filter by Group_Subsites if we've already done that
164
            $hasGroupSubsites = false;
165
            foreach ($query->getFrom() as $item) {
166
                if ((is_array($item) && strpos(
167
                    $item['table'],
168
                    'Group_Subsites'
169
                ) !== false) || (!is_array($item) && strpos(
170
                    $item,
171
                    'Group_Subsites'
172
                ) !== false)
173
                ) {
174
                    $hasGroupSubsites = true;
175
                    break;
176
                }
177
            }
178
179
            if (!$hasGroupSubsites) {
180
                if ($subsiteID) {
181
                    $query->addLeftJoin('Group_Subsites', "\"Group_Subsites\".\"GroupID\"
182
                        = \"Group\".\"ID\" AND \"Group_Subsites\".\"SubsiteID\" = $subsiteID");
183
                    $query->addWhere('("Group_Subsites"."SubsiteID" IS NOT NULL OR
184
                        "Group"."AccessAllSubsites" = 1)');
185
                } else {
186
                    $query->addWhere('"Group"."AccessAllSubsites" = 1');
187
                }
188
            }
189
190
            // WORKAROUND for databases that complain about an ORDER BY when the column wasn't selected (e.g. SQL Server)
191
            $select = $query->getSelect();
192
            if (isset($select[0]) && !$select[0] == 'COUNT(*)') {
193
                $query->addOrderBy('AccessAllSubsites', 'DESC');
194
            }
195
        }
196
    }
197
198
    public function onBeforeWrite()
199
    {
200
        // New record test approximated by checking whether the ID has changed.
201
        // Note also that the after write test is only used when we're *not* on a subsite
202
        if ($this->owner->isChanged('ID') && !SubsiteState::singleton()->getSubsiteId()) {
203
            $this->owner->AccessAllSubsites = 1;
204
        }
205
    }
206
207
    public function onAfterWrite()
208
    {
209
        // New record test approximated by checking whether the ID has changed.
210
        // Note also that the after write test is only used when we're on a subsite
211
        if ($this->owner->isChanged('ID') && $currentSubsiteID = SubsiteState::singleton()->getSubsiteId()) {
212
            $subsites = $this->owner->Subsites();
213
            $subsites->add($currentSubsiteID);
214
        }
215
    }
216
217
    public function alternateCanEdit()
218
    {
219
        // Find the sites that this group belongs to and the sites where we have appropriate perm.
220
        $accessibleSites = Subsite::accessible_sites('CMS_ACCESS_SecurityAdmin')->column('ID');
221
        $linkedSites = $this->owner->Subsites()->column('ID');
222
223
        // We are allowed to access this site if at we have CMS_ACCESS_SecurityAdmin permission on
224
        // at least one of the sites
225
        return (bool)array_intersect($accessibleSites, $linkedSites);
226
    }
227
228
    public function providePermissions()
229
    {
230
        return [
231
            'SECURITY_SUBSITE_GROUP' => [
232
                'name' => _t(__CLASS__ . '.MANAGE_SUBSITES', 'Manage subsites for groups'),
233
                'category' => _t('SilverStripe\\Security\\Permission.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
234
                'help' => _t(
235
                    __CLASS__ . '.MANAGE_SUBSITES_HELP',
236
                    'Ability to limit the permissions for a group to one or more subsites.'
237
                ),
238
                'sort' => 200
239
            ]
240
        ];
241
    }
242
}
243