Issues (144)

src/Extensions/GroupSubsites.php (1 issue)

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\DataObject;
13
use SilverStripe\ORM\DataQuery;
14
use SilverStripe\ORM\DB;
15
use SilverStripe\ORM\Queries\SQLSelect;
16
use SilverStripe\Security\Group;
17
use SilverStripe\Security\PermissionProvider;
18
use SilverStripe\Subsites\Model\Subsite;
19
use SilverStripe\Subsites\State\SubsiteState;
20
21
/**
22
 * Extension for the Group object to add subsites support
23
 *
24
 * @package subsites
25
 */
26
class GroupSubsites extends DataExtension implements PermissionProvider
27
{
28
    private static $db = [
29
        'AccessAllSubsites' => 'Boolean'
30
    ];
31
32
    private static $many_many = [
33
        'Subsites' => Subsite::class
34
    ];
35
36
    private static $defaults = [
37
        'AccessAllSubsites' => true
38
    ];
39
40
    /**
41
     * Migrations for GroupSubsites data.
42
     */
43
    public function requireDefaultRecords()
44
    {
45
        if (!$this->owner) {
46
            return;
47
        }
48
        // Migration for Group.SubsiteID data from when Groups only had a single subsite
49
        $schema = DataObject::getSchema();
50
        $groupTable = Convert::raw2sql($schema->tableName(Group::class));
51
        $groupFields = DB::field_list($groupTable);
52
53
        // Detection of SubsiteID field is the trigger for old-style-subsiteID migration
54
        if (isset($groupFields['SubsiteID'])) {
55
            // Migrate subsite-specific data
56
            DB::query('INSERT INTO "Group_Subsites" ("GroupID", "SubsiteID")
57
				SELECT "ID", "SubsiteID" FROM "' . $groupTable . '" WHERE "SubsiteID" > 0');
58
59
            // Migrate global-access data
60
            DB::query('UPDATE "' . $groupTable . '" SET "AccessAllSubsites" = 1 WHERE "SubsiteID" = 0');
61
62
            // Move the field out of the way so that this migration doesn't get executed again
63
            DB::get_schema()->renameField($groupTable, 'SubsiteID', '_obsolete_SubsiteID');
64
65
            // No subsite access on anything means that we've just installed the subsites module.
66
            // Make all previous groups global-access groups
67
        } else {
68
            if (!DB::query('SELECT "Group"."ID" FROM "' . $groupTable . '"
69
			LEFT JOIN "Group_Subsites" ON "Group_Subsites"."GroupID" = "Group"."ID" AND "Group_Subsites"."SubsiteID" > 0
70
			WHERE "AccessAllSubsites" = 1
71
			OR "Group_Subsites"."GroupID" IS NOT NULL ')->value()
72
            ) {
73
                DB::query('UPDATE "' . $groupTable . '" SET "AccessAllSubsites" = 1');
74
            }
75
        }
76
    }
77
78
    public function updateCMSFields(FieldList $fields)
79
    {
80
        if ($this->owner->canEdit()) {
81
            // i18n tab
82
            $fields->findOrMakeTab('Root.Subsites', _t(__CLASS__ . '.SECURITYTABTITLE', 'Subsites'));
83
84
            $subsites = Subsite::accessible_sites(['ADMIN', 'SECURITY_SUBSITE_GROUP'], true);
85
            $subsiteMap = $subsites->map();
86
87
            // Prevent XSS injection
88
            $subsiteMap = Convert::raw2xml($subsiteMap->toArray());
89
90
            // Interface is different if you have the rights to modify subsite group values on
91
            // all subsites
92
            if (isset($subsiteMap[0])) {
93
                $fields->addFieldToTab('Root.Subsites', new OptionsetField(
94
                    'AccessAllSubsites',
95
                    _t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
96
                    [
97
                        1 => _t(__CLASS__ . '.ACCESSALL', 'All subsites'),
98
                        0 => _t(__CLASS__ . '.ACCESSONLY', 'Only these subsites'),
99
                    ]
100
                ));
101
102
                unset($subsiteMap[0]);
103
                $fields->addFieldToTab('Root.Subsites', new CheckboxSetField(
104
                    'Subsites',
105
                    '',
106
                    $subsiteMap
107
                ));
108
            } else {
109
                if (sizeof($subsiteMap) <= 1) {
110
                    $fields->addFieldToTab('Root.Subsites', new ReadonlyField(
111
                        'SubsitesHuman',
112
                        _t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
113
                        reset($subsiteMap)
114
                    ));
115
                } else {
116
                    $fields->addFieldToTab('Root.Subsites', new CheckboxSetField(
117
                        'Subsites',
118
                        _t(__CLASS__ . '.ACCESSRADIOTITLE', 'Give this group access to'),
119
                        $subsiteMap
120
                    ));
121
                }
122
            }
123
        }
124
    }
125
126
    /**
127
     * If this group belongs to a subsite, append the subsites title to the group title to make it easy to
128
     * distinguish in the tree-view of the security admin interface.
129
     *
130
     * @param string $title
131
     */
132
    public function updateTreeTitle(&$title)
133
    {
134
        if ($this->owner->AccessAllSubsites) {
135
            $title = _t(__CLASS__ . '.GlobalGroup', 'global group');
136
            $title = htmlspecialchars($this->owner->Title, ENT_QUOTES) . ' <i>(' . $title . ')</i>';
137
        } else {
138
            $subsites = Convert::raw2xml(implode(', ', $this->owner->Subsites()->column('Title')));
139
            $title = htmlspecialchars($this->owner->Title) . " <i>($subsites)</i>";
140
        }
141
    }
142
143
    /**
144
     * Update any requests to limit the results to the current site
145
     * @param SQLSelect $query
146
     * @param DataQuery|null $dataQuery
147
     */
148
    public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
149
    {
150
        if (Subsite::$disable_subsite_filter) {
151
            return;
152
        }
153
        if (Cookie::get('noSubsiteFilter') == 'true') {
154
            return;
155
        }
156
        if ($dataQuery && $dataQuery->getQueryParam('Subsite.filter') === false) {
0 ignored issues
show
The condition $dataQuery->getQueryPara...site.filter') === false is always false.
Loading history...
157
            return;
158
        }
159
160
        // If you're querying by ID, ignore the sub-site - this is a bit ugly...
161
        if (!$query->filtersOnID()) {
162
            $subsiteID = SubsiteState::singleton()->getSubsiteId();
163
            if ($subsiteID === null) {
164
                return;
165
            }
166
167
            // Don't filter by Group_Subsites if we've already done that
168
            $hasGroupSubsites = false;
169
            foreach ($query->getFrom() as $item) {
170
                if ((is_array($item) && strpos(
171
                    $item['table'],
172
                    'Group_Subsites'
173
                ) !== false) || (!is_array($item) && strpos(
174
                    $item,
175
                    'Group_Subsites'
176
                ) !== false)
177
                ) {
178
                    $hasGroupSubsites = true;
179
                    break;
180
                }
181
            }
182
183
            if (!$hasGroupSubsites) {
184
                if ($subsiteID) {
185
                    $query->addLeftJoin('Group_Subsites', "\"Group_Subsites\".\"GroupID\"
186
                        = \"Group\".\"ID\" AND \"Group_Subsites\".\"SubsiteID\" = $subsiteID");
187
                    $query->addWhere('("Group_Subsites"."SubsiteID" IS NOT NULL OR
188
                        "Group"."AccessAllSubsites" = 1)');
189
                } else {
190
                    $query->addWhere('"Group"."AccessAllSubsites" = 1');
191
                }
192
            }
193
194
            // WORKAROUND for databases that complain about an ORDER BY when the column wasn't selected
195
            // (e.g. SQL Server)
196
            $select = $query->getSelect();
197
            if (isset($select[0]) && !$select[0] == 'COUNT(*)') {
198
                $query->addOrderBy('AccessAllSubsites', 'DESC');
199
            }
200
        }
201
    }
202
203
    public function onBeforeWrite()
204
    {
205
        // New record test approximated by checking whether the ID has changed.
206
        // Note also that the after write test is only used when we're *not* on a subsite
207
        if ($this->owner->isChanged('ID') && !SubsiteState::singleton()->getSubsiteId()) {
208
            $this->owner->AccessAllSubsites = 1;
209
        }
210
    }
211
212
    public function onAfterWrite()
213
    {
214
        // New record test approximated by checking whether the ID has changed.
215
        // Note also that the after write test is only used when we're on a subsite
216
        if ($this->owner->isChanged('ID') && $currentSubsiteID = SubsiteState::singleton()->getSubsiteId()) {
217
            $subsites = $this->owner->Subsites();
218
            $subsites->add($currentSubsiteID);
219
        }
220
    }
221
222
    public function alternateCanEdit()
223
    {
224
        // Find the sites that this group belongs to and the sites where we have appropriate perm.
225
        $accessibleSites = Subsite::accessible_sites('CMS_ACCESS_SecurityAdmin')->column('ID');
226
        $linkedSites = $this->owner->Subsites()->column('ID');
227
228
        // We are allowed to access this site if at we have CMS_ACCESS_SecurityAdmin permission on
229
        // at least one of the sites
230
        return (bool)array_intersect($accessibleSites, $linkedSites);
231
    }
232
233
    public function providePermissions()
234
    {
235
        return [
236
            'SECURITY_SUBSITE_GROUP' => [
237
                'name' => _t(__CLASS__ . '.MANAGE_SUBSITES', 'Manage subsites for groups'),
238
                'category' => _t(
239
                    'SilverStripe\\Security\\Permission.PERMISSIONS_CATEGORY',
240
                    'Roles and access permissions'
241
                ),
242
                'help' => _t(
243
                    __CLASS__ . '.MANAGE_SUBSITES_HELP',
244
                    'Ability to limit the permissions for a group to one or more subsites.'
245
                ),
246
                'sort' => 200
247
            ]
248
        ];
249
    }
250
}
251