Passed
Push — hans/addAllFields ( 30dc29...48c3f4 )
by Simon
07:26 queued 01:24
created

DataObjectExtension::getMembers()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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