Passed
Push — hans/member-based-permissions ( 6342b7...9f1b8d )
by Simon
07:24 queued 05:20
created

DataObjectExtension::onAfterDelete()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 2
b 0
f 0
nc 3
nop 0
dl 0
loc 16
ccs 6
cts 8
cp 0.75
crap 3.1406
rs 10
1
<?php
2
3
4
namespace Firesphere\SolrSearch\Extensions;
5
6
use Exception;
7
use Firesphere\SolrSearch\Helpers\SolrLogger;
8
use Firesphere\SolrSearch\Models\DirtyClass;
9
use Firesphere\SolrSearch\Services\SolrCoreService;
10
use GuzzleHttp\Exception\GuzzleException;
11
use Psr\Log\LoggerInterface;
12
use ReflectionException;
13
use SilverStripe\CMS\Model\SiteTree;
14
use SilverStripe\Control\Controller;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\ORM\ArrayList;
17
use SilverStripe\ORM\DataExtension;
18
use SilverStripe\ORM\DataList;
19
use SilverStripe\ORM\DataObject;
20
use SilverStripe\ORM\ValidationException;
21
use SilverStripe\Security\InheritedPermissionsExtension;
22
use SilverStripe\Security\Member;
23
use SilverStripe\Security\Security;
24
use SilverStripe\SiteConfig\SiteConfig;
25
use SilverStripe\Versioned\Versioned;
26
27
/**
28
 * Class \Firesphere\SolrSearch\Compat\DataObjectExtension
29
 *
30
 * Extend every DataObject with the option to update the index.
31
 *
32
 * @package Firesphere\SolrSearch\Extensions
33
 * @property DataObject|DataObjectExtension $owner
34
 */
35
class DataObjectExtension extends DataExtension
36
{
37
    /**
38
     * @var array Cached permission list
39
     */
40
    public static $cachedClasses;
41
    /**
42
     * @var SiteConfig Current siteconfig
43
     */
44
    protected static $siteConfig;
45
    /**
46
     * @var ArrayList|Member[] List of all the members in the system
47
     */
48
    protected static $memberList;
49
50
    /**
51
     * Push the item to solr if it is not versioned
52
     * Update the index after write.
53
     *
54
     * @throws ValidationException
55
     * @throws GuzzleException
56
     * @throws ReflectionException
57
     */
58 79
    public function onAfterWrite()
59
    {
60
        /** @var DataObject $owner */
61 79
        $owner = $this->owner;
62
63 79
        if ($this->shouldPush() && !$owner->hasExtension(Versioned::class)) {
64 79
            $this->pushToSolr($owner);
65
        }
66 79
    }
67
68
    /**
69
     * Should this write be pushed to Solr
70
     * @return bool
71
     */
72 79
    protected function shouldPush()
73
    {
74 79
        return !(Controller::curr()->getRequest()->getURL() &&
75 79
            strpos('dev/build', Controller::curr()->getRequest()->getURL()) !== false);
76
    }
77
78
    /**
79
     * Try to push the newly updated item to Solr
80
     *
81
     * @param DataObject $owner
82
     * @throws ValidationException
83
     * @throws GuzzleException
84
     * @throws ReflectionException
85
     */
86 79
    protected function pushToSolr(DataObject $owner)
87
    {
88 79
        $service = new SolrCoreService();
89 79
        if (!$service->isValidClass($owner->ClassName)) {
90 79
            return;
91
        }
92
        /** @var DataObject $owner */
93 2
        $record = $this->getDirtyClass($owner, SolrCoreService::UPDATE_TYPE);
94
95 2
        $ids = json_decode($record->IDs, 1) ?: [];
96 2
        $mode = Versioned::get_reading_mode();
97
        try {
98 2
            Versioned::set_reading_mode(Versioned::DEFAULT_MODE);
99 2
            $service->setInDebugMode(false);
100 2
            $type = SolrCoreService::UPDATE_TYPE;
101
            // If the object should not show in search, remove it
102 2
            if ($owner->ShowInSearch === 0) {
103
                $type = SolrCoreService::DELETE_TYPE;
104
            }
105 2
            $service->updateItems(ArrayList::create([$owner]), $type);
106
            // If we don't get an exception, mark the item as clean
107
            // Added bonus, array_flip removes duplicates
108 2
            $this->clearIDs($owner, $ids, $record);
109
        } catch (Exception $error) {
110
            Versioned::set_reading_mode($mode);
111
            $this->registerException($ids, $record, $error);
112
        }
113 2
        Versioned::set_reading_mode($mode);
114 2
    }
115
116
    /**
117
     * Find or create a new DirtyClass for recording dirty IDs
118
     *
119
     * @param DataObject $owner
120
     * @param string $type
121
     * @return DirtyClass
122
     * @throws ValidationException
123
     */
124 6
    protected function getDirtyClass(DataObject $owner, $type)
125
    {
126
        // Get the DirtyClass object for this item
127
        /** @var null|DirtyClass $record */
128 6
        $record = DirtyClass::get()->filter(['Class' => $owner->ClassName, 'Type' => $type])->first();
129 6
        if (!$record || !$record->exists()) {
130 5
            $record = DirtyClass::create([
131 5
                'Class' => $owner->ClassName,
132 5
                'Type'  => $type,
133
            ]);
134 5
            $record->write();
135
        }
136
137 6
        return $record;
138
    }
139
140
    /**
141
     * Remove the owner ID from the dirty ID set
142
     *
143
     * @param DataObject $owner
144
     * @param array $ids
145
     * @param DirtyClass $record
146
     * @throws ValidationException
147
     */
148 6
    protected function clearIDs(DataObject $owner, array $ids, DirtyClass $record): void
149
    {
150 6
        $values = array_flip($ids);
151 6
        unset($values[$owner->ID]);
152
153 6
        $record->IDs = json_encode(array_keys($values));
154 6
        $record->write();
155 6
    }
156
157
    /**
158
     * Register the exception of the attempted index for later clean-up use
159
     *
160
     * @param array $ids
161
     * @param $record
162
     * @param Exception $error
163
     * @throws ValidationException
164
     * @throws GuzzleException
165
     */
166
    protected function registerException(array $ids, $record, Exception $error): void
167
    {
168
        /** @var DataObject $owner */
169
        $owner = $this->owner;
170
        $ids[] = $owner->ID;
171
        // If we don't get an exception, mark the item as clean
172
        $record->IDs = json_encode($ids);
173
        $record->write();
174
        $logger = Injector::inst()->get(LoggerInterface::class);
175
        $logger->warn(
176
            sprintf(
177
                'Unable to alter %s with ID %s',
178
                $owner->ClassName,
179
                $owner->ID
180
            )
181
        );
182
        $solrLogger = new SolrLogger();
183
        $solrLogger->saveSolrLog('Index');
184
185
        $logger->error($error->getMessage());
186
    }
187
188
    /**
189
     * Push the item to Solr after publishing
190
     *
191
     * @throws ValidationException
192
     * @throws GuzzleException
193
     * @throws ReflectionException
194
     */
195 2
    public function onAfterPublish()
196
    {
197 2
        if ($this->shouldPush()) {
198
            /** @var DataObject $owner */
199 2
            $owner = $this->owner;
200 2
            $this->pushToSolr($owner);
201
        }
202 2
    }
203
204
    /**
205
     * Attempt to remove the item from Solr
206
     *
207
     * @throws ValidationException
208
     * @throws GuzzleException
209
     */
210 4
    public function onAfterDelete(): void
211
    {
212
        /** @var DataObject $owner */
213 4
        $owner = $this->owner;
214
        /** @var DirtyClass $record */
215 4
        $record = $this->getDirtyClass($owner, SolrCoreService::DELETE_TYPE);
216
217 4
        $ids = json_decode($record->IDs, 1) ?: [];
218
219
        try {
220 4
            (new SolrCoreService())->updateItems(ArrayList::create([$owner]), SolrCoreService::DELETE_TYPE);
221
            // If successful, remove it from the array
222
            // Added bonus, array_flip removes duplicates
223 4
            $this->clearIDs($owner, $ids, $record);
224
        } catch (Exception $error) {
225
            $this->registerException($ids, $record, $error);
226
        }
227 4
    }
228
229
    /**
230
     * Get the view status for each member in this object
231
     *
232
     * @return array
233
     */
234 7
    public function getViewStatus(): array
235
    {
236
        // return as early as possible
237
        /** @var DataObject|SiteTree $owner */
238 7
        $owner = $this->owner;
239 7
        if (isset(static::$cachedClasses[$owner->ClassName])) {
240 1
            return static::$cachedClasses[$owner->ClassName];
241
        }
242
243
        // Make sure the siteconfig is loaded
244 7
        if (!static::$siteConfig) {
245 1
            static::$siteConfig = SiteConfig::current_site_config();
246
        }
247
        // Return false if it's not allowed to show in search
248
        // The setting needs to be explicitly false, to avoid any possible collision
249
        // with objects not having the setting, thus being `null`
250
        // Return immediately if the owner has ShowInSearch not being `null`
251 7
        if ($owner->ShowInSearch === false || $owner->ShowInSearch === 0) {
252 1
            return ['false'];
253
        }
254
255 7
        $permissions = $this->getGroupViewPermissions($owner);
256
257 7
        if (!$owner->hasExtension(InheritedPermissionsExtension::class)) {
258 1
            static::$cachedClasses[$owner->ClassName] = $permissions;
259
        }
260
261 7
        return $permissions;
262
    }
263
264
    /**
265
     * Determine the view permissions based on group settings
266
     *
267
     * @param DataObject|SiteTree|SiteConfig $owner
268
     * @return array
269
     */
270 7
    protected function getGroupViewPermissions($owner): array
271
    {
272
        // Switches are not ideal, but it's a lot more readable this way!
273 7
        switch ($owner->CanViewType) {
274 7
            case 'LoggedInUsers':
275 1
                $return = ['false', 'LoggedIn'];
276 1
                break;
277 7
            case 'OnlyTheseUsers':
278 1
                $return = ['false'];
279 1
                $return = array_merge($return, $owner->ViewerGroups()->column('Code'));
280 1
                break;
281 7
            case 'Inherit':
282 7
                $parent = !$owner->ParentID ? static::$siteConfig : $owner->Parent();
283 7
                $return = $this->getGroupViewPermissions($parent);
284 7
                break;
285 7
            case 'Anyone': // View is either not implemented, or it's "Anyone"
286 7
                $return = ['null'];
287 7
                break;
288
            default:
289
                // Default to "Anyone can view"
290 1
                $return = ['null'];
291
        }
292
293 7
        return $return;
294
    }
295
296
    /**
297
     * Get permissions for viewing per member
298
     * @todo fix the implementation
299
     * @param DataObject $owner
300
     * @return array
301
     *
302
    protected function getMemberViewPermissions(DataObject $owner)
303
    {
304
        if (!static::$memberList) {
305
            static::$memberList = ArrayList::create(Member::get()->toArray());
306
        }
307
308
        $currentUser = Security::getCurrentUser();
309
        Security::setCurrentUser(null);
310
311
        if ($owner->canView(null)) {
312
            Security::setCurrentUser($currentUser);
313
            return ['null'];
314
        }
315
316
        $return = ['false'];
317
        foreach (static::$memberList as $member) {
318
            if ($owner->canView($member)) {
319
                $return[] = $member->ID;
320
            }
321
        }
322
323
        if (!$owner->hasExtension(InheritedPermissionsExtension::class)) {
324
            static::$cachedClasses[$owner->ClassName] = $return;
325
        }
326
327
        return $return;
328
    }
329
    /**/
330
}
331