Passed
Push — bufferfix ( 590e87...51cc6c )
by Simon
09:20 queued 07:07
created

DataObjectExtension::onAfterDelete()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

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