Passed
Push — hans/bufferadd ( 9532b9...4e0371 )
by Simon
07:26
created

DataObjectExtension::shouldPush()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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