Passed
Push — hans/bufferadd ( dd4951...8221cf )
by Simon
08:32 queued 06:25
created

DataObjectExtension   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 255
Duplicated Lines 0 %

Test Coverage

Coverage 77.78%

Importance

Changes 12
Bugs 0 Features 1
Metric Value
eloc 78
dl 0
loc 255
ccs 70
cts 90
cp 0.7778
rs 10
c 12
b 0
f 1
wmc 28

11 Methods

Rating   Name   Duplication   Size   Complexity  
A clearIDs() 0 7 1
A onAfterDelete() 0 16 3
A onAfterPublish() 0 6 2
A getDirtyClass() 0 14 3
A registerException() 0 20 1
A getMembers() 0 7 2
A shouldPush() 0 4 2
A pushToSolr() 0 22 4
A getViewStatus() 0 13 3
A onAfterWrite() 0 7 3
A getMemberPermissions() 0 25 4
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\Member;
22
use SilverStripe\Security\Security;
23
use SilverStripe\Versioned\Versioned;
24
25
/**
26
 * Class \Firesphere\SolrSearch\Compat\DataObjectExtension
27
 *
28
 * Extend every DataObject with the option to update the index.
29
 *
30
 * @package Firesphere\SolrSearch\Extensions
31
 * @property DataObject|DataObjectExtension $owner
32
 */
33
class DataObjectExtension extends DataExtension
34
{
35
    /**
36
     * canView cache
37
     *
38
     * @var array
39
     */
40
    public static $canViewClasses = [];
41
    /**
42
     * Member cache
43
     *
44
     * @var DataList
45
     */
46
    protected static $members;
47
48
    /**
49
     * Push the item to solr if it is not versioned
50
     * Update the index after write.
51
     *
52
     * @throws ValidationException
53
     * @throws GuzzleException
54
     * @throws ReflectionException
55
     */
56 77
    public function onAfterWrite()
57
    {
58
        /** @var DataObject $owner */
59 77
        $owner = $this->owner;
60
61 77
        if ($this->shouldPush() && !$owner->hasExtension(Versioned::class)) {
62 77
            $this->pushToSolr($owner);
63
        }
64 77
    }
65
66
    /**
67
     * Should this write be pushed to Solr
68
     *
69
     * @return bool
70
     */
71 77
    protected function shouldPush()
72
    {
73 77
        return !(Controller::curr()->getRequest()->getURL() &&
74 77
            strpos('dev/build', Controller::curr()->getRequest()->getURL()) !== false);
75
    }
76
77
    /**
78
     * Try to push the newly updated item to Solr
79
     *
80
     * @param DataObject $owner
81
     * @throws ValidationException
82
     * @throws GuzzleException
83
     * @throws ReflectionException
84
     */
85 77
    protected function pushToSolr(DataObject $owner): void
86
    {
87 77
        $service = new SolrCoreService();
88 77
        if (!$service->isValidClass($owner->ClassName)) {
89 77
            return;
90
        }
91
        /** @var DataObject $owner */
92 2
        $record = $this->getDirtyClass($owner, SolrCoreService::UPDATE_TYPE);
93
94 2
        $ids = json_decode($record->IDs, 1) ?: [];
95 2
        $mode = Versioned::get_reading_mode();
96
        try {
97 2
            Versioned::set_reading_mode(Versioned::DEFAULT_MODE);
98 2
            $service->setInDebugMode(false);
99 2
            $service->updateItems(ArrayList::create([$owner]), SolrCoreService::UPDATE_TYPE);
100
            // If we don't get an exception, mark the item as clean
101
            // Added bonus, array_flip removes duplicates
102 2
            $this->clearIDs($owner, $ids, $record);
103 2
            Versioned::set_reading_mode($mode);
104
        } catch (Exception $error) {
105
            Versioned::set_reading_mode($mode);
106
            $this->registerException($ids, $record, $error);
107
        }
108 2
    }
109
110
    /**
111
     * Find or create a new DirtyClass for recording dirty IDs
112
     *
113
     * @param DataObject $owner
114
     * @param string $type
115
     * @return DirtyClass
116
     * @throws ValidationException
117
     */
118 6
    protected function getDirtyClass(DataObject $owner, $type)
119
    {
120
        // Get the DirtyClass object for this item
121
        /** @var null|DirtyClass $record */
122 6
        $record = DirtyClass::get()->filter(['Class' => $owner->ClassName, 'Type' => $type])->first();
123 6
        if (!$record || !$record->exists()) {
124 5
            $record = DirtyClass::create([
125 5
                'Class' => $owner->ClassName,
126 5
                'Type'  => $type,
127
            ]);
128 5
            $record->write();
129
        }
130
131 6
        return $record;
132
    }
133
134
    /**
135
     * Remove the owner ID from the dirty ID set
136
     *
137
     * @param DataObject $owner
138
     * @param array $ids
139
     * @param DirtyClass $record
140
     * @throws ValidationException
141
     */
142 6
    protected function clearIDs(DataObject $owner, array $ids, DirtyClass $record): void
143
    {
144 6
        $values = array_flip($ids);
145 6
        unset($values[$owner->ID]);
146
147 6
        $record->IDs = json_encode(array_keys($values));
148 6
        $record->write();
149 6
    }
150
151
    /**
152
     * Register the exception of the attempted index for later clean-up use
153
     *
154
     * @param array $ids
155
     * @param $record
156
     * @param Exception $error
157
     * @throws ValidationException
158
     * @throws GuzzleException
159
     */
160
    protected function registerException(array $ids, $record, Exception $error): void
161
    {
162
        /** @var DataObject $owner */
163
        $owner = $this->owner;
164
        $ids[] = $owner->ID;
165
        // If we don't get an exception, mark the item as clean
166
        $record->IDs = json_encode($ids);
167
        $record->write();
168
        $logger = Injector::inst()->get(LoggerInterface::class);
169
        $logger->warn(
170
            sprintf(
171
                'Unable to alter %s with ID %s',
172
                $owner->ClassName,
173
                $owner->ID
174
            )
175
        );
176
        $solrLogger = new SolrLogger();
177
        $solrLogger->saveSolrLog('Index');
178
179
        $logger->error($error->getMessage());
180
    }
181
182
    /**
183
     * Push the item to Solr after publishing
184
     *
185
     * @throws ValidationException
186
     * @throws GuzzleException
187
     * @throws ReflectionException
188
     */
189 2
    public function onAfterPublish()
190
    {
191 2
        if ($this->shouldPush()) {
192
            /** @var DataObject $owner */
193 2
            $owner = $this->owner;
194 2
            $this->pushToSolr($owner);
195
        }
196 2
    }
197
198
    /**
199
     * Attempt to remove the item from Solr
200
     *
201
     * @throws ValidationException
202
     * @throws GuzzleException
203
     */
204 4
    public function onAfterDelete(): void
205
    {
206
        /** @var DataObject $owner */
207 4
        $owner = $this->owner;
208
        /** @var DirtyClass $record */
209 4
        $record = $this->getDirtyClass($owner, SolrCoreService::DELETE_TYPE);
210
211 4
        $ids = json_decode($record->IDs, 1) ?: [];
212 4
        parent::onAfterDelete();
213
        try {
214 4
            (new SolrCoreService())->updateItems(ArrayList::create([$owner]), SolrCoreService::DELETE_TYPE);
215
            // If successful, remove it from the array
216
            // Added bonus, array_flip removes duplicates
217 4
            $this->clearIDs($owner, $ids, $record);
218
        } catch (Exception $error) {
219
            $this->registerException($ids, $record, $error);
220
        }
221 4
    }
222
223
    /**
224
     * Get the view status for each member in this object
225
     *
226
     * @return array
227
     */
228 7
    public function getViewStatus(): array
229
    {
230
        // Return empty if it's not allowed to show in search
231
        // The setting needs to be explicitly false, to avoid any possible collision
232
        // with objects not having the setting, thus being `null`
233
        /** @var DataObject|SiteTree $owner */
234 7
        $owner = $this->owner;
235
        // Return immediately if the owner has ShowInSearch not being `null`
236 7
        if ($owner->ShowInSearch === false || $owner->ShowInSearch === 0) {
237 1
            return [];
238
        }
239
240 7
        return self::$canViewClasses[$owner->ClassName] ?? $this->getMemberPermissions($owner);
241
    }
242
243
    /**
244
     * Get the view permissions for each member in the system
245
     *
246
     * @param DataObject|SiteTree $owner
247
     * @return array
248
     */
249 4
    protected function getMemberPermissions($owner): array
250
    {
251
        // Log out the current user to avoid collisions in permissions
252 4
        $currMember = Security::getCurrentUser();
253 4
        Security::setCurrentUser(null);
254
255 4
        if ($owner->canView(null)) {
256 3
            self::$canViewClasses[$owner->ClassName] = ['1-null'];
257
258
            // Anyone can view
259 3
            return ['1-null'];
260
        }
261
        // Return a default '0-0' to basically say "noboday can view"
262 2
        $return = ['0-0'];
263 2
        foreach (self::getMembers() as $member) {
264 2
            $return[] = sprintf('%s-%s', (int)$owner->canView($member), (int)$member->ID);
265
        }
266
267 2
        if (!$owner->hasField('ShowInSearch')) {
268 1
            self::$canViewClasses[$owner->ClassName] = $return;
269
        }
270
271 2
        Security::setCurrentUser($currMember);
272
273 2
        return $return;
274
    }
275
276
    /**
277
     * Get the static list of members
278
     *
279
     * @return DataList
280
     */
281 2
    protected static function getMembers()
282
    {
283 2
        if (empty(self::$members)) {
284 1
            self::$members = Member::get();
285
        }
286
287 2
        return self::$members;
288
    }
289
}
290