diff --git a/.craftplugin b/.craftplugin index 0354cb5..86f077e 100644 --- a/.craftplugin +++ b/.craftplugin @@ -1 +1,31 @@ -{"pluginName":"Elasticsearch","pluginDescription":"Bring the power of Elasticsearch to you Craft 3 CMS project","pluginVersion":"1.1.0,1.0.0","pluginAuthorName":"La Haute Société","pluginVendorName":"la-haute-societe","pluginAuthorUrl":"https://www.lahautesociete.com","pluginAuthorGithub":"juban","codeComments":"codeComments","pluginComponents":["utilities","jobs","tasks","consolecommands","controllers","cpsection","models","records","services","settings","variables"],"consolecommandName":"Elasticsearch","controllerName":"Elasticsearch","cpsectionName":"Elasticsearch","modelName":"Elasticsearch","recordName":"Elasticsearch","serviceName":"Elasticsearch","utilityName":"elasticsearch","apiVersion":"api_version_3_0"} +{ + "pluginName": "Elasticsearch", + "pluginDescription": "Bring the power of Elasticsearch to you Craft 5 CMS project", + "pluginVersion": "1.1.0,1.0.0", + "pluginAuthorName": "La Haute Société", + "pluginVendorName": "la-haute-societe", + "pluginAuthorUrl": "https://www.lahautesociete.com", + "pluginAuthorGithub": "juban", + "codeComments": "codeComments", + "pluginComponents": [ + "utilities", + "jobs", + "tasks", + "consolecommands", + "controllers", + "cpsection", + "models", + "records", + "services", + "settings", + "variables" + ], + "consolecommandName": "Elasticsearch", + "controllerName": "Elasticsearch", + "cpsectionName": "Elasticsearch", + "modelName": "Elasticsearch", + "recordName": "Elasticsearch", + "serviceName": "Elasticsearch", + "utilityName": "elasticsearch", + "apiVersion": "api_version_3_0" +} diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index a17970c..0fcdeb0 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ !.vscode/extensions.json config.codekit3 prepros-6.config +composer.lock \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100755 new mode 100644 diff --git a/LICENSE.md b/LICENSE.md old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 index d47312d..9eb92a6 --- a/README.md +++ b/README.md @@ -9,11 +9,15 @@ Bring the power of Elasticsearch to your Craft CMS projects. ## Requirements -This plugin works with **Craft CMS 3 or 4**. +This plugin works with **Craft CMS 5**. Use v2 for **Craft CMS 3 or 4** In order to index data, you will need an **Elasticsearch 6.0** (or later) instance, with the **Ingest attachment processor** plugin activated. +In order to use **Elasticsearch 8.0** (or later) you will need to set the +[yii2-elasticsearch][yii2-elasticsearch] dslVersion to 8. The default +dslVersion is set at 5. Setting the dslVersion can be done inside the +elasticsearchComponentConfig. ## Installation diff --git a/composer.json b/composer.json old mode 100755 new mode 100644 index c9172f4..e717d60 --- a/composer.json +++ b/composer.json @@ -23,9 +23,13 @@ } ], "require": { - "craftcms/cms": "^4.0.0", + "craftcms/cms": "^5.0", "yiisoft/yii2-elasticsearch": "^2.1.0" }, + "require-dev": { + "craftcms/phpstan": "dev-main", + "craftcms/rector": "dev-main" + }, "autoload": { "psr-4": { "lhs\\elasticsearch\\": "src/" @@ -37,5 +41,13 @@ "schemaVersion": "1.3.0", "changelogUrl": "https://raw.githubusercontent.com/la-haute-societe/craft-elasticsearch/master/CHANGELOG.md", "class": "lhs\\elasticsearch\\Elasticsearch" - } + }, + "config": { + "allow-plugins": { + "yiisoft/yii2-composer": true, + "craftcms/plugin-installer": true + } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..f9f81af --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - vendor/craftcms/phpstan/phpstan.neon + +parameters: + level: 0 + paths: + - src diff --git a/src/Elasticsearch.php b/src/Elasticsearch.php old mode 100755 new mode 100644 index f91acbe..8d0e9ca --- a/src/Elasticsearch.php +++ b/src/Elasticsearch.php @@ -1,8 +1,6 @@ controllerNamespace = 'lhs\elasticsearch\console\controllers'; } - if (Craft::$app->getRequest()->getIsCpRequest()) { + $isCpRequest = Craft::$app->getRequest()->getIsCpRequest(); + $isConsoleRequest = Craft::$app->getRequest()->getIsConsoleRequest(); + if ($isCpRequest || $isConsoleRequest) { // Remove entry from the index upon deletion Event::on( Entry::class, @@ -100,6 +100,21 @@ function (Event $event) { } ); + // Remove asset from the index upon deletion + Event::on( + Asset::class, + Asset::EVENT_AFTER_DELETE, + function (Event $event) { + /** @var Asset $asset */ + $asset = $event->sender; + try { + $this->elementIndexerService->deleteElement($asset); + } catch (Exception $e) { + // Noop, the element must have already been deleted + } + } + ); + // Index entry, asset & products upon save (creation or update) Event::on(Entry::class, Entry::EVENT_AFTER_SAVE, [$this, 'onElementSaved']); Event::on(Asset::class, Asset::EVENT_AFTER_SAVE, [$this, 'onElementSaved']); @@ -134,7 +149,7 @@ function (ExecEvent $event) { // Register the plugin's CP utility Event::on( Utilities::class, - Utilities::EVENT_REGISTER_UTILITY_TYPES, + Utilities::EVENT_REGISTER_UTILITIES, function (RegisterComponentTypesEvent $event) { $event->types[] = RefreshElasticsearchIndexUtility::class; } @@ -237,7 +252,7 @@ protected function settingsHtml(): string $overrides = Craft::$app->getConfig()->getConfigFromFile(strtolower($this->handle)); $sections = ArrayHelper::map( - Craft::$app->sections->getAllSections(), + Craft::$app->getEntries()->getAllSections(), 'id', function (Section $section): array { return [ @@ -378,8 +393,12 @@ public function onElementSaved(ModelEvent $event): void } if ($notDraftOrRevision) { - if ($element->enabled) { - $this->reindexQueueManagementService->enqueueJob($element->id, $element->siteId, get_class($element)); + if ($element->enabled && $element->getEnabledForSite()) { + + // Only index entry items with an uri. This prevents jobs being spawned for Matrix entries in Craft 5. + if (! $element instanceof Entry || $element->uri) { + $this->reindexQueueManagementService->enqueueJob($element->id, $element->siteId, get_class($element)); + } } else { try { $this->elementIndexerService->deleteElement($element); diff --git a/src/config.php b/src/config.php old mode 100755 new mode 100644 index 5f74889..a3c369a --- a/src/config.php +++ b/src/config.php @@ -1,8 +1,6 @@ getMessage(); } - return ElasticsearchPlugin::getInstance()->elementIndexerService->indexElement($element); + return $element + ? ElasticsearchPlugin::getInstance()->elementIndexerService->indexElement($element) + : null; } } diff --git a/src/controllers/CpController.php b/src/controllers/CpController.php index 8e5e7b5..d677355 100644 --- a/src/controllers/CpController.php +++ b/src/controllers/CpController.php @@ -1,8 +1,6 @@ getSiteIds($request); - Elasticsearch::getInstance()->indexManagementService->recreateSiteIndex(...$siteIds); + + // Don't match exactly. On false we receive an empty string, on true we + // receive a string '1'. We only recreate the indexes when requested. + if ($params['recreate'] == true) { + Elasticsearch::getInstance()->indexManagementService->recreateSiteIndex(...$siteIds); + } } catch (\Exception $e) { return $this->asErrorJson($e->getMessage()); } @@ -181,7 +184,9 @@ protected function reindexElement(): ?string $element = $model->getElement(); try { - return Elasticsearch::getInstance()->elementIndexerService->indexElement($element); + return $element + ? Elasticsearch::getInstance()->elementIndexerService->indexElement($element) + : null; } catch (\Exception $e) { Craft::error("Error while re-indexing element {$element->url}: {$e->getMessage()}", __METHOD__); Craft::error(VarDumper::dumpAsString($e), __METHOD__); diff --git a/src/events/ErrorEvent.php b/src/events/ErrorEvent.php index fbdeae3..de17c71 100644 --- a/src/events/ErrorEvent.php +++ b/src/events/ErrorEvent.php @@ -1,8 +1,6 @@ elementId = $this->elementId; $model->siteId = $this->siteId; $model->type = $this->type; - Elasticsearch::getInstance()->elementIndexerService->indexElement($model->getElement()); + + if ($element = $model->getElement()) { + Elasticsearch::getInstance()->elementIndexerService->indexElement($element); + } } /** diff --git a/src/migrations/m200929_155818_project_config_support.php b/src/migrations/m200929_155818_project_config_support.php index 3dc1b20..55d225f 100644 --- a/src/migrations/m200929_155818_project_config_support.php +++ b/src/migrations/m200929_155818_project_config_support.php @@ -21,7 +21,7 @@ public function safeUp() $ids = $projectConfig->get('plugins.elasticsearch.settings.blacklistedEntryTypes'); $IdToHandleMapping = ArrayHelper::map( - Craft::$app->sections->getAllEntryTypes(), + Craft::$app->getEntries()->getAllEntryTypes(), static function (EntryType $entryType) { return $entryType->id; }, static function (EntryType $entryType) { return $entryType->handle; } ); diff --git a/src/models/IndexableElementModel.php b/src/models/IndexableElementModel.php index 1583168..ab2adcf 100644 --- a/src/models/IndexableElementModel.php +++ b/src/models/IndexableElementModel.php @@ -21,7 +21,7 @@ class IndexableElementModel extends \craft\base\Model implements \JsonSerializab public $type; /** - * @return Element + * @return Element|null * @throws IndexableElementModelException */ public function getElement() @@ -51,15 +51,10 @@ public function getElement() throw new IndexableElementModelException($this, IndexableElementModelException::UNEXPECTED_TYPE); } - if ($element === null) { - throw new IndexableElementModelException($this, IndexableElementModelException::ELEMENT_NOT_FOUND); - } - return $element; } - - public function jsonSerialize() + public function jsonSerialize(): mixed { return $this->toArray(); } diff --git a/src/models/SettingsModel.php b/src/models/SettingsModel.php old mode 100755 new mode 100644 index da353b5..31659bb --- a/src/models/SettingsModel.php +++ b/src/models/SettingsModel.php @@ -1,8 +1,6 @@ put(static::index(), ['include_type_name' => 'false'], Json::encode($schema)); + // https://www.elastic.co/blog/moving-from-types-to-typeless-apis-in-elasticsearch-7-0 + if ($db->dslVersion >= 7) { + $db->put(static::index(), [], Json::encode($schema)); + } else { + $db->put(static::index(), ['include_type_name' => 'false'], Json::encode($schema)); + } } /** diff --git a/src/resources/CpAssetBundle.php b/src/resources/CpAssetBundle.php index 91e53ab..2cdd621 100644 --- a/src/resources/CpAssetBundle.php +++ b/src/resources/CpAssetBundle.php @@ -1,8 +1,6 @@ hasContent()) { - $message = "Not indexing entry #{$element->id} since it has no content."; - Craft::debug($message, __METHOD__); - return $message; - } - if (!$element->getUrl()) { $message = "Not indexing entry #{$element->id} since it has no URL."; Craft::debug($message, __METHOD__); @@ -161,6 +153,15 @@ protected function getReasonForNotReindexing(Element $element): ?string } } + if ($element instanceof Asset) { + $blacklist = $this->plugin->getSettings()->blacklistedAssetVolumes; + if (in_array($element->getVolume()->handle, $blacklist, true)) { + $message = "Not indexing asset #{$element->id} since it's in a blacklisted asset volume."; + Craft::debug($message, __METHOD__); + return $message; + } + } + return null; } diff --git a/src/services/IndexManagementService.php b/src/services/IndexManagementService.php index c5ac287..5e9dcf3 100644 --- a/src/services/IndexManagementService.php +++ b/src/services/IndexManagementService.php @@ -1,8 +1,6 @@ queue->push($job); - $this->addJobIdToCache($jobId); + if ($jobId > 0) { + $this->addJobIdToCache($jobId); + } } /** diff --git a/src/templates/components/elastic-branding.twig b/src/templates/components/elastic-branding.twig index 073f996..c985004 100644 --- a/src/templates/components/elastic-branding.twig +++ b/src/templates/components/elastic-branding.twig @@ -1,9 +1,7 @@ {# @var craft \craft\web\twig\variables\CraftVariable #} {# /** - * Elasticsearch plugin for Craft CMS 3.x - * - * Bring the power of Elasticsearch to you Craft 3 CMS project + * Elasticsearch plugin for Craft CMS. * * @link https://www.lahautesociete.com * @copyright Copyright (c) 2018 La Haute Société diff --git a/src/templates/cp/settings.twig b/src/templates/cp/settings.twig old mode 100755 new mode 100644 index 3c1cc7e..b7df81f --- a/src/templates/cp/settings.twig +++ b/src/templates/cp/settings.twig @@ -1,9 +1,7 @@ {# @var craft \craft\web\twig\variables\CraftVariable #} {# /** - * Elasticsearch plugin for Craft CMS 3.x - * - * Bring the power of Elasticsearch to you Craft 3 CMS project + * Elasticsearch plugin for Craft CMS. * * @link https://www.lahautesociete.com * @copyright Copyright (c) 2018 La Haute Société @@ -137,7 +135,7 @@ - {% for section in craft.app.sections.allSections %} + {% for section in craft.app.entries.allSections %} {% for entryType in section.entryTypes %} {{ section.type|ucfirst }} diff --git a/src/templates/cp/utility.twig b/src/templates/cp/utility.twig old mode 100755 new mode 100644 index d23dc2f..a91e40e --- a/src/templates/cp/utility.twig +++ b/src/templates/cp/utility.twig @@ -1,9 +1,7 @@ {# @var craft \craft\web\twig\variables\CraftVariable #} {# /** - * Elasticsearch plugin for Craft CMS 3.x - * - * Bring the power of Elasticsearch to you Craft 3 CMS project + * Elasticsearch plugin for Craft CMS. * * @link https://www.lahautesociete.com * @copyright Copyright (c) 2018 La Haute Société @@ -32,6 +30,16 @@ > {{ csrfInput() }} +

{{ 'Index'|t('elasticsearch') }}

+ +
+ {{ forms.checkbox({ + name: 'recreate', + label: 'Recreate the index'|t('elasticsearch'), + info: 'Recreating the index removes all existing data, it may take a while before all content is available again.'|t('elasticsearch'), + }) }} +
+

{{ 'Sites'|t('elasticsearch') }}

{{ forms.checkboxSelect({ name: 'sites', diff --git a/src/translations/fr/elasticsearch.php b/src/translations/fr/elasticsearch.php old mode 100755 new mode 100644 index d11b95b..2f919b4 --- a/src/translations/fr/elasticsearch.php +++ b/src/translations/fr/elasticsearch.php @@ -1,8 +1,6 @@