Passed
Push — hans/code-cleanup ( 74c548...547d18 )
by Simon
09:31 queued 07:32
created

DataObjectExtension   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Test Coverage

Coverage 79%

Importance

Changes 10
Bugs 0 Features 1
Metric Value
eloc 91
dl 0
loc 259
ccs 79
cts 100
cp 0.79
rs 9.84
c 10
b 0
f 1
wmc 32

10 Methods

Rating   Name   Duplication   Size   Complexity  
A onAfterPublish() 0 6 2
A shouldPush() 0 4 2
A pushToSolr() 0 28 5
A clearIDs() 0 7 1
A registerException() 0 20 1
A onAfterDelete() 0 16 3
A getDirtyClass() 0 14 3
A getViewStatus() 0 28 6
A onAfterWrite() 0 7 3
A getGroupViewPermissions() 0 24 6
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\DataObject;
19
use SilverStripe\ORM\ValidationException;
20
use SilverStripe\Security\InheritedPermissionsExtension;
21
use SilverStripe\Security\Member;
22
use SilverStripe\SiteConfig\SiteConfig;
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
     * @var array Cached permission list
37
     */
38
    public static $cachedClasses;
39
    /**
40
     * @var SiteConfig Current siteconfig
41
     */
42
    protected static $siteConfig;
43
    /**
44
     * @var ArrayList|Member[] List of all the members in the system
45
     */
46
    protected static $memberList;
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 80
    public function onAfterWrite()
57
    {
58
        /** @var DataObject $owner */
59 80
        $owner = $this->owner;
60
61 80
        if ($this->shouldPush() && !$owner->hasExtension(Versioned::class)) {
62 80
            $this->pushToSolr($owner);
63
        }
64 80
    }
65
66
    /**
67
     * Should this write be pushed to Solr
68
     * @return bool
69
     */
70 80
    protected function shouldPush()
71
    {
72 80
        return !(Controller::curr()->getRequest()->getURL() &&
73 80
            strpos('dev/build', Controller::curr()->getRequest()->getURL()) !== false);
74
    }
75
76
    /**
77
     * Try to push the newly updated item to Solr
78
     *
79
     * @param DataObject $owner
80
     * @throws ValidationException
81
     * @throws GuzzleException
82
     * @throws ReflectionException
83
     */
84 80
    protected function pushToSolr(DataObject $owner)
85
    {
86 80
        $service = new SolrCoreService();
87 80
        if (!$service->isValidClass($owner->ClassName)) {
88 80
            return;
89
        }
90
        /** @var DataObject $owner */
91 2
        $record = $this->getDirtyClass($owner, SolrCoreService::UPDATE_TYPE);
92
93 2
        $ids = json_decode($record->IDs, 1) ?: [];
94 2
        $mode = Versioned::get_reading_mode();
95
        try {
96 2
            Versioned::set_reading_mode(Versioned::DEFAULT_MODE);
97 2
            $service->setInDebugMode(false);
98 2
            $type = SolrCoreService::UPDATE_TYPE;
99
            // If the object should not show in search, remove it
100 2
            if ($owner->ShowInSearch === 0) {
101
                $type = SolrCoreService::DELETE_TYPE;
102
            }
103 2
            $service->updateItems(ArrayList::create([$owner]), $type);
104
            // If we don't get an exception, mark the item as clean
105
            // Added bonus, array_flip removes duplicates
106 2
            $this->clearIDs($owner, $ids, $record);
107
        } catch (Exception $error) {
108
            Versioned::set_reading_mode($mode);
109
            $this->registerException($ids, $record, $error);
110
        }
111 2
        Versioned::set_reading_mode($mode);
112 2
    }
113
114
    /**
115
     * Find or create a new DirtyClass for recording dirty IDs
116
     *
117
     * @param DataObject $owner
118
     * @param string $type
119
     * @return DirtyClass
120
     * @throws ValidationException
121
     */
122 6
    protected function getDirtyClass(DataObject $owner, $type)
123
    {
124
        // Get the DirtyClass object for this item
125
        /** @var null|DirtyClass $record */
126 6
        $record = DirtyClass::get()->filter(['Class' => $owner->ClassName, 'Type' => $type])->first();
127 6
        if (!$record || !$record->exists()) {
128 5
            $record = DirtyClass::create([
129 5
                'Class' => $owner->ClassName,
130 5
                'Type'  => $type,
131
            ]);
132 5
            $record->write();
133
        }
134
135 6
        return $record;
136
    }
137
138
    /**
139
     * Remove the owner ID from the dirty ID set
140
     *
141
     * @param DataObject $owner
142
     * @param array $ids
143
     * @param DirtyClass $record
144
     * @throws ValidationException
145
     */
146 6
    protected function clearIDs(DataObject $owner, array $ids, DirtyClass $record): void
147
    {
148 6
        $values = array_flip($ids);
149 6
        unset($values[$owner->ID]);
150
151 6
        $record->IDs = json_encode(array_keys($values));
152 6
        $record->write();
153 6
    }
154
155
    /**
156
     * Register the exception of the attempted index for later clean-up use
157
     *
158
     * @param array $ids
159
     * @param $record
160
     * @param Exception $error
161
     * @throws ValidationException
162
     * @throws GuzzleException
163
     */
164
    protected function registerException(array $ids, $record, Exception $error): void
165
    {
166
        /** @var DataObject $owner */
167
        $owner = $this->owner;
168
        $ids[] = $owner->ID;
169
        // If we don't get an exception, mark the item as clean
170
        $record->IDs = json_encode($ids);
171
        $record->write();
172
        $logger = Injector::inst()->get(LoggerInterface::class);
173
        $logger->warn(
174
            sprintf(
175
                'Unable to alter %s with ID %s',
176
                $owner->ClassName,
177
                $owner->ID
178
            )
179
        );
180
        $solrLogger = new SolrLogger();
181
        $solrLogger->saveSolrLog('Index');
182
183
        $logger->error($error->getMessage());
184
    }
185
186
    /**
187
     * Push the item to Solr after publishing
188
     *
189
     * @throws ValidationException
190
     * @throws GuzzleException
191
     * @throws ReflectionException
192
     */
193 2
    public function onAfterPublish()
194
    {
195 2
        if ($this->shouldPush()) {
196
            /** @var DataObject $owner */
197 2
            $owner = $this->owner;
198 2
            $this->pushToSolr($owner);
199
        }
200 2
    }
201
202
    /**
203
     * Attempt to remove the item from Solr
204
     *
205
     * @throws ValidationException
206
     * @throws GuzzleException
207
     */
208 4
    public function onAfterDelete(): void
209
    {
210
        /** @var DataObject $owner */
211 4
        $owner = $this->owner;
212
        /** @var DirtyClass $record */
213 4
        $record = $this->getDirtyClass($owner, SolrCoreService::DELETE_TYPE);
214
215 4
        $ids = json_decode($record->IDs, 1) ?: [];
216
217
        try {
218 4
            (new SolrCoreService())->updateItems(ArrayList::create([$owner]), SolrCoreService::DELETE_TYPE);
219
            // If successful, remove it from the array
220
            // Added bonus, array_flip removes duplicates
221 4
            $this->clearIDs($owner, $ids, $record);
222
        } catch (Exception $error) {
223
            $this->registerException($ids, $record, $error);
224
        }
225 4
    }
226
227
    /**
228
     * Get the view status for each member in this object
229
     *
230
     * @return array
231
     */
232 7
    public function getViewStatus(): array
233
    {
234
        // return as early as possible
235
        /** @var DataObject|SiteTree $owner */
236 7
        $owner = $this->owner;
237 7
        if (isset(static::$cachedClasses[$owner->ClassName])) {
238 1
            return static::$cachedClasses[$owner->ClassName];
239
        }
240
241
        // Make sure the siteconfig is loaded
242 7
        if (!static::$siteConfig) {
243 1
            static::$siteConfig = SiteConfig::current_site_config();
244
        }
245
        // Return false if it's not allowed to show in search
246
        // The setting needs to be explicitly false, to avoid any possible collision
247
        // with objects not having the setting, thus being `null`
248
        // Return immediately if the owner has ShowInSearch not being `null`
249 7
        if ($owner->ShowInSearch === false || $owner->ShowInSearch === 0) {
250 1
            return ['false'];
251
        }
252
253 7
        $permissions = $this->getGroupViewPermissions($owner);
254
255 7
        if (!$owner->hasExtension(InheritedPermissionsExtension::class)) {
256 1
            static::$cachedClasses[$owner->ClassName] = $permissions;
257
        }
258
259 7
        return $permissions;
260
    }
261
262
    /**
263
     * Determine the view permissions based on group settings
264
     *
265
     * @param DataObject|SiteTree|SiteConfig $owner
266
     * @return array
267
     */
268 7
    protected function getGroupViewPermissions($owner): array
269
    {
270
        // Switches are not ideal, but it's a lot more readable this way!
271 7
        switch ($owner->CanViewType) {
272 7
            case 'LoggedInUsers':
273 1
                $return = ['false', 'LoggedIn'];
274 1
                break;
275 7
            case 'OnlyTheseUsers':
276 1
                $return = ['false'];
277 1
                $return = array_merge($return, $owner->ViewerGroups()->column('Code'));
278 1
                break;
279 7
            case 'Inherit':
280 7
                $parent = !$owner->ParentID ? static::$siteConfig : $owner->Parent();
281 7
                $return = $this->getGroupViewPermissions($parent);
282 7
                break;
283 7
            case 'Anyone': // View is either not implemented, or it's "Anyone"
284 7
                $return = ['null'];
285 7
                break;
286
            default:
287
                // Default to "Anyone can view"
288 1
                $return = ['null'];
289
        }
290
291 7
        return $return;
292
    }
293
}
294