Passed
Push — hans/can-you-see-me-now ( 3ba251...3ce8de )
by Simon
07:34
created

DataObjectExtension   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Test Coverage

Coverage 79%

Importance

Changes 11
Bugs 0 Features 1
Metric Value
eloc 90
dl 0
loc 254
ccs 79
cts 100
cp 0.79
rs 9.84
c 11
b 0
f 1
wmc 32

10 Methods

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