Firesphere /
silverstripe-elastic
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * class DataObjectExtension|Firesphere\ElasticSearch\Extensions\DataObjectExtension Adds checking if changes should be |
||
| 4 | * pushed to Elastic |
||
| 5 | * |
||
| 6 | * @package Firesphere\Elastic\Search |
||
| 7 | * @author Simon `Firesphere` Erkelens; Marco `Sheepy` Hermo |
||
| 8 | * @copyright Copyright (c) 2018 - now() Firesphere & Sheepy |
||
| 9 | */ |
||
| 10 | |||
| 11 | namespace Firesphere\ElasticSearch\Extensions; |
||
| 12 | |||
| 13 | use Elastic\Elasticsearch\Exception\ClientResponseException; |
||
| 14 | use Elastic\Elasticsearch\Exception\ServerResponseException; |
||
| 15 | use Elastic\Elasticsearch\Response\Elasticsearch; |
||
| 16 | use Exception; |
||
| 17 | use Firesphere\ElasticSearch\Indexes\ElasticIndex; |
||
| 18 | use Firesphere\ElasticSearch\Services\ElasticCoreService; |
||
| 19 | use Firesphere\SearchBackend\Extensions\DataObjectSearchExtension; |
||
| 20 | use Http\Promise\Promise; |
||
| 21 | use Psr\Container\NotFoundExceptionInterface; |
||
| 22 | use Psr\Log\LoggerInterface; |
||
| 23 | use SilverStripe\CMS\Model\SiteTree; |
||
| 24 | use SilverStripe\Core\Injector\Injector; |
||
| 25 | use SilverStripe\ORM\ArrayList; |
||
| 26 | use SilverStripe\ORM\DataExtension; |
||
| 27 | use SilverStripe\ORM\DataObject; |
||
| 28 | use SilverStripe\Versioned\Versioned; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * Class \Firesphere\ElasticSearch\Extensions\DataObjectElasticExtension |
||
| 32 | * |
||
| 33 | * @property DataObject|DataObjectElasticExtension $owner |
||
| 34 | */ |
||
| 35 | class DataObjectElasticExtension extends DataExtension |
||
| 36 | { |
||
| 37 | protected $deletedFromElastic; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * @throws NotFoundExceptionInterface |
||
| 41 | */ |
||
| 42 | public function onAfterDelete() |
||
| 43 | { |
||
| 44 | parent::onAfterDelete(); |
||
| 45 | $this->deleteFromElastic(); |
||
| 46 | } |
||
| 47 | |||
| 48 | /** |
||
| 49 | * Can be called directly, if a DataObject needs to be removed |
||
| 50 | * immediately. |
||
| 51 | * @return bool|Elasticsearch|Promise |
||
| 52 | * @throws NotFoundExceptionInterface |
||
| 53 | */ |
||
| 54 | public function deleteFromElastic() |
||
| 55 | { |
||
| 56 | $result = false; |
||
| 57 | $service = Injector::inst()->get(ElasticCoreService::class); |
||
| 58 | $indexes = $service->getValidIndexes(); |
||
| 59 | foreach ($indexes as $index) { |
||
| 60 | /** @var ElasticIndex $idx */ |
||
| 61 | $idx = Injector::inst()->get($index); |
||
| 62 | $config = ElasticIndex::config()->get($idx->getIndexName()); |
||
| 63 | if (in_array($this->owner->ClassName, $config['Classes'])) { |
||
| 64 | $deleteQuery = $this->getDeleteQuery($idx); |
||
| 65 | $result = $this->executeQuery($service, $deleteQuery); |
||
| 66 | } |
||
| 67 | } |
||
| 68 | |||
| 69 | return $result; |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * @param ElasticIndex $index |
||
| 74 | * @return array |
||
| 75 | */ |
||
| 76 | private function getDeleteQuery(ElasticIndex $index): array |
||
| 77 | { |
||
| 78 | return [ |
||
| 79 | 'index' => $index->getIndexName(), |
||
| 80 | 'body' => [ |
||
| 81 | 'query' => [ |
||
| 82 | 'match' => [ |
||
| 83 | 'id' => sprintf('%s-%s', $this->owner->ClassName, $this->owner->ID) |
||
| 84 | ] |
||
| 85 | ] |
||
| 86 | ] |
||
| 87 | ]; |
||
| 88 | } |
||
| 89 | |||
| 90 | /** |
||
| 91 | * @param ElasticCoreService $service |
||
| 92 | * @param array $deleteQuery |
||
| 93 | * @return Elasticsearch|Promise|bool |
||
| 94 | * @throws NotFoundExceptionInterface |
||
| 95 | */ |
||
| 96 | protected function executeQuery(ElasticCoreService $service, array $deleteQuery) |
||
| 97 | { |
||
| 98 | try { |
||
| 99 | return $service->getClient()->deleteByQuery($deleteQuery); |
||
| 100 | } catch (Exception $e) { |
||
| 101 | /** @var DataObjectSearchExtension|DataObject $owner */ |
||
| 102 | $owner = $this->owner; |
||
| 103 | // DirtyClass handling is a DataObject Search Core extension |
||
| 104 | $dirty = $owner->getDirtyClass('DELETE'); |
||
| 105 | $ids = json_decode($dirty->IDs ?? '[]'); |
||
| 106 | $ids[] = $owner->ID; |
||
| 107 | $dirty->IDs = json_encode($ids); |
||
| 108 | $dirty->write(); |
||
| 109 | /** @var LoggerInterface $logger */ |
||
| 110 | $logger = Injector::inst()->get(LoggerInterface::class); |
||
| 111 | $logger->error($e->getMessage(), $e->getTrace()); |
||
| 112 | |||
| 113 | return false; |
||
| 114 | } |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Reindex after write, if it's an indexed new/updated object |
||
| 119 | * @throws ClientResponseException |
||
| 120 | * @throws NotFoundExceptionInterface |
||
| 121 | * @throws ServerResponseException |
||
| 122 | */ |
||
| 123 | public function onAfterWrite() |
||
| 124 | { |
||
| 125 | parent::onAfterWrite(); |
||
| 126 | /** @var DataObject|SiteTree|DataObjectElasticExtension|DataObjectSearchExtension|Versioned $owner */ |
||
| 127 | $owner = $this->owner; |
||
| 128 | if ($this->shouldPush($owner)) { |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 129 | $this->pushToElastic(); |
||
| 130 | } |
||
| 131 | |||
| 132 | if ($owner->hasField('ShowInSearch') && |
||
| 133 | $owner->isChanged('ShowInSearch') && |
||
| 134 | !$owner->ShowInSearch |
||
| 135 | ) { |
||
| 136 | $this->deletedFromElastic = $this->deleteFromElastic(); |
||
| 137 | } |
||
| 138 | } |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Check if: |
||
| 142 | * - Owner has Versioned |
||
| 143 | * - The versioned object is published |
||
| 144 | * - The owner has the "ShowInSearch" Field |
||
| 145 | * - And if so, is it set. |
||
| 146 | * @param SiteTree|DataObjectSearchExtension|DataObjectElasticExtension|Versioned|DataObject $owner |
||
| 147 | * @return bool |
||
| 148 | */ |
||
| 149 | public function shouldPush(DataObject $owner): bool |
||
| 150 | { |
||
| 151 | $showInSearch = true; |
||
| 152 | $versioned = $owner->hasExtension(Versioned::class); |
||
| 153 | if ($versioned) { |
||
| 154 | $versioned = $owner->isPublished(); |
||
| 155 | } else { |
||
| 156 | // The owner is not versioned, so no publishing check |
||
| 157 | $versioned = true; |
||
| 158 | } |
||
| 159 | $hasField = $owner->hasField('ShowInSearch'); |
||
| 160 | if ($hasField) { |
||
| 161 | $showInSearch = $owner->ShowInSearch; |
||
| 162 | } |
||
| 163 | |||
| 164 | return ($versioned && $showInSearch); |
||
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * This is a separate method from the delete action, as it's a different route |
||
| 169 | * and query components. |
||
| 170 | * It can be called to add an object to the index immediately, without |
||
| 171 | * requiring a write. |
||
| 172 | * @return mixed |
||
| 173 | * @throws ClientResponseException |
||
| 174 | * @throws NotFoundExceptionInterface |
||
| 175 | * @throws ServerResponseException |
||
| 176 | */ |
||
| 177 | public function pushToElastic() |
||
| 178 | { |
||
| 179 | $result = false; |
||
| 180 | $list = ArrayList::create(); |
||
| 181 | $list->push($this->owner); |
||
| 182 | /** @var ElasticCoreService $service */ |
||
| 183 | $service = Injector::inst()->get(ElasticCoreService::class); |
||
| 184 | foreach ($service->getValidIndexes() as $indexStr) { |
||
| 185 | /** @var ElasticIndex $index */ |
||
| 186 | $index = Injector::inst()->get($indexStr); |
||
| 187 | $idxConfig = ElasticIndex::config()->get($index->getIndexName()); |
||
| 188 | if (in_array($this->owner->ClassName, $idxConfig['Classes'])) { |
||
| 189 | $result = $service->updateIndex($index, $list); |
||
| 190 | } |
||
| 191 | } |
||
| 192 | |||
| 193 | return $result; |
||
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Add ability to see what the response |
||
| 198 | * from Elasticsearch was after a delete action. |
||
| 199 | * |
||
| 200 | * @return mixed |
||
| 201 | */ |
||
| 202 | public function isDeletedFromElastic() |
||
| 203 | { |
||
| 204 | return $this->deletedFromElastic; |
||
| 205 | } |
||
| 206 | } |
||
| 207 |