From b12b10407266e1379bf12c209cf4c0a94908dbc6 Mon Sep 17 00:00:00 2001 From: Tom Schulze Date: Wed, 15 Jun 2022 15:34:02 +0200 Subject: [PATCH] Craft 4 compatibility --- CHANGELOG.md | 10 ++ README.md | 6 +- composer.json | 9 +- src/Elastic.php | 63 +++----- .../controllers/ElementsController.php | 2 +- src/console/controllers/IndexController.php | 41 +++--- src/jobs/DeleteOrphanedIndexes.php | 2 +- src/jobs/ReindexUpdatedElements.php | 10 +- src/jobs/UpdateDatabaseIndex.php | 21 +-- src/jobs/UpdateElasticsearchIndex.php | 21 +-- src/jobs/UpdateMapping.php | 2 +- src/models/Settings.php | 18 +-- src/services/Elasticsearch.php | 12 +- src/services/Elements.php | 8 +- src/services/Indexes.php | 26 ++-- src/services/Search.php | 137 +++--------------- src/utilities/IndexUtility.php | 4 +- 17 files changed, 134 insertions(+), 258 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff5ebb..e1f1cc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Release Notes for Elasticsearch Plugin +## 2.0.0 - 2022-06-15 + +### Added + +- Craft CMS 4 compatibility + +### Changed + +- Requires Craft CMS >= 4.0 + ## 1.1.0 - 2022-04-27 ### Added diff --git a/README.md b/README.md index 33ceb12..6be2bb4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Replace Craft's database full-text search with Elasticsearch. ## Requirements - * Craft CMS >= 3.6.18 +- Craft CMS >= 3.6.18 ## Installation @@ -17,9 +17,11 @@ Open your terminal and go to your Craft project: ``` shell cd /path/to/project composer require codemonauts/craft-elasticsearch -./craft install/plugin elastic +./craft plugin/install elastic ``` +You can also install the plugin via the Plugin Store in the Craft Control Panel. + ## Usage For detailed usage information, [visit the docs](https://plugins.codemonauts.com/plugins/elasticsearch/Introduction.html). diff --git a/composer.json b/composer.json index f9d62e2..92e03d4 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "codemonauts/craft-elasticsearch", "description": "Replace Craft's database full-text search with Elasticsearch.", - "version": "1.1.0", + "version": "2.0.0", "type": "craft-plugin", "keywords": [ "craft", @@ -23,7 +23,7 @@ "issues": "https://github.com/codemonauts/craft-elasticsearch/issues" }, "require": { - "craftcms/cms": "^3.6.18", + "craftcms/cms": "^4.0.0", "elasticsearch/elasticsearch": "^7.0", "jsq/amazon-es-php": "^0.3.0" }, @@ -35,9 +35,6 @@ "extra": { "handle": "elastic", "class": "codemonauts\\elastic\\Elastic", - "name": "Search powered by Elasticsearch", - "description": "Replace Craft's database full-text search with Elasticsearch.", - "changelogUrl": "https://raw.githubusercontent.com/codemonauts/craft-elasticsearch/blob/main/CHANGELOG.md", - "documentationUrl": "https://plugins.codemonauts.com/plugins/elasticsearch/Introduction.html" + "name": "Search powered by Elasticsearch" } } diff --git a/src/Elastic.php b/src/Elastic.php index efab0ad..f22ed59 100644 --- a/src/Elastic.php +++ b/src/Elastic.php @@ -11,7 +11,6 @@ use codemonauts\elastic\services\Search; use codemonauts\elastic\utilities\IndexUtility; use Craft; -use craft\base\ElementInterface; use craft\base\Plugin; use craft\events\ElementEvent; use craft\events\ModelEvent; @@ -32,19 +31,19 @@ class Elastic extends Plugin { /** - * @var Elastic + * @var Elastic|null */ - public static $plugin; + public static ?Elastic $plugin; /** - * @var Settings + * @var Settings|null */ - public static $settings; + public static ?Settings $settings; /** * @inheritDoc */ - public $hasCpSettings = true; + public bool $hasCpSettings = true; /** * @inheritDoc @@ -58,7 +57,7 @@ public function init(): void self::$settings = self::$plugin->getSettings(); // If no endpoint is set, do nothing. - if (Elastic::env(self::$settings->endpoint) === '') { + if (App::parseEnv(self::$settings->endpoint) === '') { return; } @@ -75,16 +74,16 @@ public function init(): void 'elasticsearch' => [ 'class' => Elasticsearch::class, 'hosts' => [ - Elastic::env(self::$settings->endpoint), + App::parseEnv(self::$settings->endpoint), ], 'authentication' => self::$settings->authentication, - 'username' => Elastic::env(self::$settings->username), - 'password' => Elastic::env(self::$settings->password), - 'region' => Elastic::env(self::$settings->region), + 'username' => App::parseEnv(self::$settings->username), + 'password' => App::parseEnv(self::$settings->password), + 'region' => App::parseEnv(self::$settings->region), ], 'indexes' => [ 'class' => Indexes::class, - 'indexName' => Elastic::env(self::$settings->indexName), + 'indexName' => App::parseEnv(self::$settings->indexName), ], 'elements' => services\Elements::class, 'search' => Search::class, @@ -93,10 +92,7 @@ public function init(): void // When in transition mode, add event to update Elasticsearch indexes as well. if (self::$settings->transition) { - Craft::$app->elements->on(Elements::EVENT_AFTER_SAVE_ELEMENT, function(ElementEvent $event) { - /** - * @var ElementInterface $element - */ + Craft::$app->elements->on(Elements::EVENT_AFTER_SAVE_ELEMENT, function (ElementEvent $event) { $element = $event->element; $elementType = get_class($element); @@ -112,17 +108,17 @@ public function init(): void } // Register event when changing field definitions - Craft::$app->fields->on(Fields::EVENT_AFTER_SAVE_FIELD, function() { + Craft::$app->fields->on(Fields::EVENT_AFTER_SAVE_FIELD, function () { Craft::$app->queue->push(new UpdateMapping()); }); // Register utilities - Craft::$app->getUtilities()->on(Utilities::EVENT_REGISTER_UTILITY_TYPES, function(RegisterComponentTypesEvent $event) { + Craft::$app->getUtilities()->on(Utilities::EVENT_REGISTER_UTILITY_TYPES, function (RegisterComponentTypesEvent $event) { $event->types[] = IndexUtility::class; }); // Register settings event - $this->on(Plugin::EVENT_BEFORE_SAVE_SETTINGS, function(ModelEvent $event) { + $this->on(Plugin::EVENT_BEFORE_SAVE_SETTINGS, function (ModelEvent $event) { $settings = $event->sender->getSettings(); // Mode has changed @@ -146,7 +142,7 @@ public function init(): void /** * @inheritDoc */ - public function afterInstall() + protected function afterInstall(): void { parent::afterInstall(); @@ -162,7 +158,7 @@ public function afterInstall() /** * @inheritDoc */ - protected function createSettingsModel() + protected function createSettingsModel(): Settings { return new Settings(); } @@ -176,7 +172,7 @@ protected function settingsHtml(): ?string 'settings' => $this->getSettings(), 'authenticationOptions' => [ 'aws' => 'AWS', - 'basicauth' => 'BasicAuth' + 'basicauth' => 'BasicAuth', ], 'boostsCols' => [ 'handle' => [ @@ -192,29 +188,6 @@ protected function settingsHtml(): ?string ); } - /** - * Parse environment string. Should be replaced with Craft's App::parseEnv after dropping 3.6 support. - * - * @param string|null $str The string to parse. - * - * @return array|string|null - */ - public static function env(string $str = null) - { - if ($str === null) { - return null; - } - - if (preg_match('/^\$(\w+)$/', $str, $matches)) { - $value = App::env($matches[1]); - if ($value !== false) { - $str = $value; - } - } - - return $str; - } - public function getElasticsearch(): Elasticsearch { return $this->get('elasticsearch'); diff --git a/src/console/controllers/ElementsController.php b/src/console/controllers/ElementsController.php index fe6f714..9f07ad2 100644 --- a/src/console/controllers/ElementsController.php +++ b/src/console/controllers/ElementsController.php @@ -67,7 +67,7 @@ public function actionIndex(string $siteHandle = '*', string $queue = 'queue', i ]); try { $queue->priority($priority)->push($job); - } catch (NotSupportedException $e) { + } catch (NotSupportedException) { $queue->push($job); } Console::updateProgress(++$counter, $total); diff --git a/src/console/controllers/IndexController.php b/src/console/controllers/IndexController.php index 0ace2a3..1b33615 100644 --- a/src/console/controllers/IndexController.php +++ b/src/console/controllers/IndexController.php @@ -39,9 +39,10 @@ public function options($actionID): array /** * Outputs some stats of all or a specific indices. * - * @param string|null $siteHandle Default '*' to get stats of all sites. + * @param string $siteHandle Default '*' to get stats of all sites. * - * @throws SiteNotFoundException|InvalidConfigException + * @throws \craft\errors\SiteNotFoundException + * @throws \yii\base\InvalidConfigException */ public function actionStats(string $siteHandle = '*') { @@ -57,7 +58,7 @@ public function actionStats(string $siteHandle = '*') $this->stdout('Current index in use: ' . $indexName . PHP_EOL); $this->stdout('Elements in index: ' . $result['indices'][$indexName]['total']['docs']['count'] . PHP_EOL); $this->stdout('Stored data: ' . Craft::$app->getFormatter()->asShortSize($result['indices'][$indexName]['total']['store']['size_in_bytes']) . PHP_EOL . PHP_EOL); - } catch (Missing404Exception $e) { + } catch (Missing404Exception) { $this->stderr('Index for site "'); $this->stderr($site->handle, BaseConsole::FG_YELLOW); $this->stderr('" not found.' . PHP_EOL); @@ -69,9 +70,10 @@ public function actionStats(string $siteHandle = '*') * Outputs the source of an element in the index. * * @param int $elementId The element ID to output. - * @param string|null $siteHandle The site to use. + * @param string $siteHandle The site to use. * - * @throws SiteNotFoundException|InvalidConfigException + * @throws \craft\errors\SiteNotFoundException + * @throws \yii\base\InvalidConfigException */ public function actionSource(int $elementId, string $siteHandle = '*') { @@ -103,7 +105,7 @@ public function actionSource(int $elementId, string $siteHandle = '*') $rows[] = [$indexService->mapFieldToAttribute($field), $source]; } echo $table->setRows($rows)->run() . PHP_EOL . PHP_EOL; - } catch (Missing404Exception $e) { + } catch (Missing404Exception) { $this->stdout('Element not indexed!' . PHP_EOL, BaseConsole::FG_RED); } } @@ -174,7 +176,7 @@ public function actionReindex(string $siteHandle = null) return; } - $timeTook = $result['took'] > 1000 ? DateTimeHelper::secondsToHumanTimeDuration(round($result['took']/1000)) : $result['took'] . 'ms'; + $timeTook = $result['took'] > 1000 ? DateTimeHelper::secondsToHumanTimeDuration(round($result['took'] / 1000)) : $result['took'] . 'ms'; $this->stdout('Old index: ' . $result['oldIndexName'] . PHP_EOL); $this->stdout('New index: ' . $result['newIndexName'] . PHP_EOL); @@ -240,7 +242,7 @@ public function actionList() $table->setHeaders(['Alias', 'Current index']); $rows = []; foreach ($result['aliases'] as $alias) { - if (strpos($alias['alias'], '.') === 0) { + if (str_starts_with($alias['alias'], '.')) { continue; } $rows[] = [ @@ -254,22 +256,15 @@ public function actionList() $table->setHeaders(['Health', 'Index', 'Status', 'Documents', 'Size']); $rows = []; foreach ($result['indexes'] as $index) { - if (strpos($index['index'], '.') === 0) { + if (str_starts_with($index['index'], '.')) { continue; } - switch ($index['health']) { - case 'red': - $format = [Console::FG_RED]; - break; - case 'yellow': - $format = [Console::FG_YELLOW]; - break; - case 'green': - $format = [Console::FG_GREEN]; - break; - default: - $format = [Console::FG_GREY]; - } + $format = match ($index['health']) { + 'red' => [Console::FG_RED], + 'yellow' => [Console::FG_YELLOW], + 'green' => [Console::FG_GREEN], + default => [Console::FG_GREY], + }; $rows[] = [ Console::ansiFormat($index['health'], $format), $index['index'], @@ -287,7 +282,7 @@ public function actionList() * * @param string|null $siteHandle * - * @return array|Site[] + * @return Site[] * @throws SiteNotFoundException */ private function _getSites(string $siteHandle = null): array diff --git a/src/jobs/DeleteOrphanedIndexes.php b/src/jobs/DeleteOrphanedIndexes.php index addf26d..a6b936c 100644 --- a/src/jobs/DeleteOrphanedIndexes.php +++ b/src/jobs/DeleteOrphanedIndexes.php @@ -12,7 +12,7 @@ class DeleteOrphanedIndexes extends BaseJob /** * @inheritDoc */ - public function execute($queue) + public function execute($queue): void { $sites = Craft::$app->getSites()->getAllSites(); $elements = Elastic::$plugin->getElements(); diff --git a/src/jobs/ReindexUpdatedElements.php b/src/jobs/ReindexUpdatedElements.php index 61b81bd..19a22bc 100644 --- a/src/jobs/ReindexUpdatedElements.php +++ b/src/jobs/ReindexUpdatedElements.php @@ -12,24 +12,24 @@ class ReindexUpdatedElements extends BaseJob { /** - * @var DateTime|null + * @var DateTime */ - public $startDate; + public DateTime $startDate; /** * @var bool Whether to reindex elements to the database full-text index. */ - public $toDatabaseIndex = false; + public bool $toDatabaseIndex = false; /** * @var bool Whether to reindex elements to the Elasticsearch index. */ - public $toElasticsearchIndex = false; + public bool $toElasticsearchIndex = false; /** * @inheritDoc */ - public function execute($queue) + public function execute($queue): void { $query = new Query(); $query->select(['id', 'type']) diff --git a/src/jobs/UpdateDatabaseIndex.php b/src/jobs/UpdateDatabaseIndex.php index 2a20ce7..363d516 100644 --- a/src/jobs/UpdateDatabaseIndex.php +++ b/src/jobs/UpdateDatabaseIndex.php @@ -11,39 +11,34 @@ class UpdateDatabaseIndex extends BaseJob /** * @var string|ElementInterface|null The type of elements to update. */ - public $elementType; + public string|ElementInterface|null $elementType; /** * @var int|int[]|null The ID(s) of the element(s) to update */ - public $elementId; + public int|array|null $elementId; /** * @var int|string|null The site ID of the elements to update, or `'*'` to update all sites */ - public $siteId = '*'; + public int|string|null $siteId = '*'; /** * @inheritDoc */ - public function execute($queue) + public function execute($queue): void { $class = $this->elementType; $search = Craft::$app->getSearch(); - $query = $class::find() + $elements = $class::find() ->drafts(null) ->id($this->elementId) ->siteId($this->siteId) - ->anyStatus(); + ->provisionalDrafts(null) + ->status(null) + ->all(); - // TODO: Remove when dropping 3.6 support. - $craft37 = version_compare(Craft::$app->getVersion(), '3.7', '>='); - if ($craft37) { - $query->provisionalDrafts(null); - } - - $elements = $query->all(); $total = count($elements); foreach ($elements as $i => $element) { diff --git a/src/jobs/UpdateElasticsearchIndex.php b/src/jobs/UpdateElasticsearchIndex.php index 5f4952f..dd18a97 100644 --- a/src/jobs/UpdateElasticsearchIndex.php +++ b/src/jobs/UpdateElasticsearchIndex.php @@ -12,39 +12,34 @@ class UpdateElasticsearchIndex extends BaseJob /** * @var string|ElementInterface|null The type of elements to update. */ - public $elementType; + public string|ElementInterface|null $elementType; /** * @var int|int[]|null The ID(s) of the element(s) to update */ - public $elementId; + public int|array|null $elementId; /** * @var int|string|null The site ID of the elements to update, or `'*'` to update all sites */ - public $siteId = '*'; + public int|string|null $siteId = '*'; /** * @inheritDoc */ - public function execute($queue) + public function execute($queue): void { $class = $this->elementType; $search = Elastic::$plugin->getSearch(); - $query = $class::find() + $elements = $class::find() ->drafts(null) ->id($this->elementId) ->siteId($this->siteId) - ->anyStatus(); + ->status(null) + ->provisionalDrafts(null) + ->all(); - // TODO: Remove when dropping 3.6 support. - $craft37 = version_compare(Craft::$app->getVersion(), '3.7', '>='); - if ($craft37) { - $query->provisionalDrafts(null); - } - - $elements = $query->all(); $total = count($elements); foreach ($elements as $i => $element) { diff --git a/src/jobs/UpdateMapping.php b/src/jobs/UpdateMapping.php index 03305a1..f1ce9bc 100644 --- a/src/jobs/UpdateMapping.php +++ b/src/jobs/UpdateMapping.php @@ -11,7 +11,7 @@ class UpdateMapping extends BaseJob /** * @inheritDoc */ - public function execute($queue) + public function execute($queue): void { $sites = Craft::$app->sites->getAllSites(); $indexes = Elastic::$plugin->getIndexes(); diff --git a/src/models/Settings.php b/src/models/Settings.php index fa9256d..8e1f048 100644 --- a/src/models/Settings.php +++ b/src/models/Settings.php @@ -27,43 +27,43 @@ class Settings extends Model /** * @var string The endpoint URL to use. */ - public $endpoint = ''; + public string $endpoint = ''; /** * @var string|null The authentication method to use. Valid values are 'aws' for IAM credentials or instance * profiles and 'basicauth' for all other realms with username and password authentication. */ - public $authentication = null; + public ?string $authentication = null; /** * @var string|null The username or IAM access key. */ - public $username = null; + public ?string $username = null; /** * @var string|null The password or IAM secret key. */ - public $password = null; + public ?string $password = null; /** * @var string|null The AWS region the AWS OpenSearch domain is in. */ - public $region = null; + public ?string $region = null; /** * @var string The index name to use. It will be prepended to every site's handle. */ - public $indexName = 'craftcms'; + public string $indexName = 'craftcms'; /** * @var string Prefix for all field handles. It prevents collisions with reserved names. */ - public $fieldPrefix = 'craft_'; + public string $fieldPrefix = 'craft_'; /** - * @var array Boosts for fields. + * @var array|null Boosts for fields. */ - public $fieldBoosts = null; + public ?array $fieldBoosts = null; /** * @inheritdoc diff --git a/src/services/Elasticsearch.php b/src/services/Elasticsearch.php index dc3f3ee..71b1725 100644 --- a/src/services/Elasticsearch.php +++ b/src/services/Elasticsearch.php @@ -18,32 +18,32 @@ class Elasticsearch extends Component /** * @var string|null The authentication to use. You can use 'realm' or 'aws'. */ - public $authentication; + public ?string $authentication; /** * @var string[]|null The Elasticsearch hosts to connect to. */ - public $hosts; + public ?array $hosts; /** * @var string|null The username to use for authentication. Leave blank if you use AWS Elasticsearch/OpenSearch and instance profile to authenticate. */ - public $username; + public ?string $username; /** * @var string|null The password to use for authentication. */ - public $password; + public ?string $password; /** * @var string|null The AWS domain region. */ - public $region; + public ?string $region; /** * @var Client|null The elasticsearch client. */ - private $client; + private ?Client $client = null; /** * Returns the Elasticsearch client. diff --git a/src/services/Elements.php b/src/services/Elements.php index 648643d..4d79cfa 100644 --- a/src/services/Elements.php +++ b/src/services/Elements.php @@ -27,7 +27,7 @@ class Elements extends Component * @return array|callable * @throws InvalidConfigException */ - public function add(ElementInterface $element, Site $site, array $keywords) + public function add(ElementInterface $element, Site $site, array $keywords): callable|array { $fieldPrefix = Elastic::$settings->fieldPrefix; $indexes = Elastic::$plugin->getIndexes(); @@ -83,7 +83,7 @@ public function delete(int $elementId, Site $site) * @return array|callable * @throws InvalidConfigException */ - public function search(SearchQuery $searchQuery, array $scope, Site $site) + public function search(SearchQuery $searchQuery, array $scope, Site $site): callable|array { $indexes = Elastic::$plugin->getIndexes(); $settings = Elastic::$settings; @@ -128,7 +128,7 @@ public function search(SearchQuery $searchQuery, array $scope, Site $site) $fields = ['*']; if (is_array($settings->fieldBoosts)) { foreach ($settings->fieldBoosts as $field) { - $fields[] = $indexes->mapAttributeToField($field['handle']).'^'.$field['boost']; + $fields[] = $indexes->mapAttributeToField($field['handle']) . '^' . $field['boost']; } } @@ -202,7 +202,7 @@ public function getAllIndexedIds(Site $site): array * @return array|callable|false * @throws InvalidConfigException */ - public function bulkDelete(array $ids, Site $site) + public function bulkDelete(array $ids, Site $site): callable|bool|array { if (empty($ids)) { return false; diff --git a/src/services/Indexes.php b/src/services/Indexes.php index a5ab49e..0ab2be5 100644 --- a/src/services/Indexes.php +++ b/src/services/Indexes.php @@ -18,12 +18,12 @@ class Indexes extends Component /** * @var string The name to use for the indexes. */ - public $indexName; + public string $indexName; /** * @var string[] Mapping of Elasticsearch field names to Craft field handles. */ - private $fieldToAttribute = []; + private array $fieldToAttribute = []; // Functions to create and delete index structure for Craft's search @@ -97,7 +97,7 @@ public function deleteIndexOfSite(Site $site): array * @return array|false * @throws InvalidConfigException */ - public function reIndexSite(Site $site) + public function reIndexSite(Site $site): bool|array { $returnValue = []; @@ -169,7 +169,7 @@ public function cloneToSite(Site $site, string $sourceAlias): bool try { $result = Elastic::$plugin->getElasticsearch()->getClient()->indices()->clone($params); - } catch (Exception $e) { + } catch (Exception) { $result = ['acknowledged' => false]; } @@ -305,7 +305,7 @@ public function buildIndexConfiguration(Site $site): array * @return int|string * @throws InvalidConfigException */ - public function getCurrentIndex(Site $site) + public function getCurrentIndex(Site $site): int|string { return $this->getIndexOfAlias($this->getIndexName($site)); } @@ -318,7 +318,7 @@ public function getCurrentIndex(Site $site) * @return int|string * @throws InvalidConfigException */ - public function getIndexOfAlias(string $aliasName) + public function getIndexOfAlias(string $aliasName): int|string { $params = [ 'name' => $aliasName, @@ -439,10 +439,10 @@ public function setIndexBlockWrite(string $indexName, bool $status): bool * * @param Site $site The site to return the current mapping for. * - * @return array|mixed - * @throws InvalidConfigException + * @return mixed + * @throws \yii\base\InvalidConfigException */ - public function getCurrentMapping(Site $site) + public function getCurrentMapping(Site $site): mixed { $currentIndex = $this->getCurrentIndex($site); $result = Elastic::$plugin->getElasticsearch()->getClient()->indices()->getMapping(['index' => $currentIndex]); @@ -592,9 +592,9 @@ public function mapFieldToAttribute(string $fieldName): string $fieldPrefix = Elastic::$settings->fieldPrefix; $attributeNeedle = $fieldPrefix . 'attribute_'; $fieldNeedle = $fieldPrefix . 'field_'; - if (strpos($fieldName, $attributeNeedle) === 0) { + if (str_starts_with($fieldName, $attributeNeedle)) { $this->fieldToAttribute[$fieldName] = substr($fieldName, strlen($attributeNeedle)); - } else if (strpos($fieldName, $fieldNeedle) === 0) { + } else if (str_starts_with($fieldName, $fieldNeedle)) { $id = (int)substr($fieldName, strlen($fieldNeedle)); $field = Craft::$app->getFields()->getFieldById($id); if (!$field) { @@ -644,7 +644,7 @@ public function stats(Site $site): array * @return array|callable * @throws InvalidConfigException */ - public function source(int $elementId, Site $site) + public function source(int $elementId, Site $site): callable|array { $params = [ 'index' => $this->getIndexName($site), @@ -660,7 +660,7 @@ public function source(int $elementId, Site $site) * @return array * @throws InvalidConfigException */ - public function list() + public function list(): array { $aliases = Elastic::$plugin->getElasticsearch()->getClient()->cat()->aliases(); $indexes = Elastic::$plugin->getElasticsearch()->getClient()->cat()->indices(); diff --git a/src/services/Search.php b/src/services/Search.php index a198ac5..e0562e8 100644 --- a/src/services/Search.php +++ b/src/services/Search.php @@ -5,54 +5,30 @@ use codemonauts\elastic\Elastic; use codemonauts\elastic\jobs\DeleteOrphanedIndexes; use Craft; -use craft\base\Component; use craft\base\ElementInterface; use craft\base\FieldInterface; use craft\elements\db\ElementQuery; -use craft\errors\InvalidFieldException; use craft\errors\SiteNotFoundException; use craft\events\SearchEvent; -use craft\helpers\Search as SearchHelper; +use craft\helpers\ArrayHelper; use craft\search\SearchQuery; +use craft\services\Search as CraftSearch; use Elasticsearch\Common\Exceptions\BadRequest400Exception; -use yii\base\InvalidConfigException; /** * Handles search in backend with Elasticsearch */ -class Search extends Component +class Search extends CraftSearch { - /** - * @event SearchEvent The event that is triggered before a search is performed. - */ - const EVENT_BEFORE_SEARCH = 'beforeSearch'; - - /** - * @event SearchEvent The event that is triggered after a search is performed. - */ - const EVENT_AFTER_SEARCH = 'afterSearch'; - - /** - * @var bool Whether full-text searches should be used ever. We will always use full-text search :) - * @since 3.4.10 - */ - public $useFullText = true; - /** * @var int|null The minimum word length that keywords must be in order to use a full-text search. Defaults to 2. */ - public $minFullTextWordLength; + public ?int $minFullTextWordLength; /** - * Indexes the attributes of a given element defined by its element type. - * - * @param ElementInterface $element - * @param string[]|null $fieldHandles Only for compatibility. We always index all searchable fields and attributes. - * - * @return bool Whether the indexing was a success. - * @throws SiteNotFoundException|InvalidFieldException|InvalidConfigException + * @inheritDoc */ - public function indexElementAttributes(ElementInterface $element, array $fieldHandles = null): bool + public function indexElementAttributes(ElementInterface $element, ?array $fieldHandles = null): bool { $elementsService = Elastic::$plugin->getElements(); @@ -75,7 +51,7 @@ public function indexElementAttributes(ElementInterface $element, array $fieldHa /** @var FieldInterface[] $updateFields */ $updateFields = []; if ($element::hasContent() && ($fieldLayout = $element->getFieldLayout()) !== null) { - foreach ($fieldLayout->getFields() as $field) { + foreach ($fieldLayout->getCustomFields() as $field) { if ($field->searchable) { $updateFields[] = $field; } @@ -111,97 +87,33 @@ public function indexElementAttributes(ElementInterface $element, array $fieldHa } /** - * Indexes the field values for a given element and site. - * - * @param int $elementId The ID of the element getting indexed. - * @param int $siteId The site ID of the content getting indexed. - * @param array $fields The field values, indexed by field ID. - * - * @return bool Whether the indexing was a success. - * @throws SiteNotFoundException|InvalidFieldException|InvalidConfigException - * @deprecated in 3.4.0. Use [[indexElementAttributes()]] instead. - */ - public function indexElementFields(int $elementId, int $siteId, array $fields): bool - { - $element = Craft::$app->elements->getElementById($elementId, null, $siteId); - - return $this->indexElementAttributes($element, $fields); - } - - /** - * Searches for elements that match the given element query. - * - * @param ElementQuery $elementQuery The element query being executed - * - * @return array The filtered list of element IDs. - * @throws InvalidConfigException - * @since 3.7.14 + * @inheritDoc */ public function searchElements(ElementQuery $elementQuery): array { - return $this->_searchElements($elementQuery, null, $elementQuery->search, $elementQuery->siteId, $elementQuery->customFields); - } - - /** - * Filters a list of element IDs by a given search query. - * - * @param int[] $elementIds The list of element IDs to filter by the search query. - * @param string|array|SearchQuery $searchQuery The search query (either a string or a SearchQuery instance) - * @param bool $scoreResults Whether to order the results based on how closely they match the query. (No longer checked.) - * @param int|int[]|null $siteId The site ID(s) to filter by. - * @param bool $returnScores Whether the search scores should be included in the results. If true, results will be returned as `element ID => score`. - * @param FieldInterface[]|null $customFields The custom fields involved in the query. - * - * @return array The filtered list of element IDs. - * @throws InvalidConfigException - * @deprecated in 3.7.14. Use [[searchElements()]] instead. - */ - public function filterElementIdsByQuery(array $elementIds, $searchQuery, bool $scoreResults = true, $siteId = null, bool $returnScores = false, ?array $customFields = null): array - { - $scoredResults = $this->_searchElements(null, $elementIds, $searchQuery, $siteId, $customFields); - return $returnScores ? $scoredResults : array_keys($scoredResults); - } - - /** - * Filters a list of element IDs by a given search query. - * - * @param ElementQuery|null $elementQuery - * @param int[]|null $elementIds - * @param string|array|SearchQuery $searchQuery - * @param int|int[]|null $siteId - * @param FieldInterface[]|null $customFields - * - * @return array - * @throws InvalidConfigException - */ - private function _searchElements(?ElementQuery $elementQuery, ?array $elementIds, $searchQuery, $siteId, ?array $customFields): array - { - if ($elementQuery !== null) { - $elementQuery = (clone $elementQuery) - ->search(null) - ->offset(null) - ->limit(null); - } - + $searchQuery = $elementQuery->search; if (is_string($searchQuery)) { $searchQuery = new SearchQuery($searchQuery, Craft::$app->getConfig()->getGeneral()->defaultSearchTermOptions); - } else if (is_array($searchQuery)) { - $options = $searchQuery; - $searchQuery = $options['query']; - unset($options['query']); + } elseif (is_array($searchQuery)) { + $options = array_merge($searchQuery); + $searchQuery = ArrayHelper::remove($options, 'query'); $options = array_merge(Craft::$app->getConfig()->getGeneral()->defaultSearchTermOptions, $options); $searchQuery = new SearchQuery($searchQuery, $options); } - $site = Craft::$app->getSites()->getSiteById($siteId); + $elementQuery = (clone $elementQuery) + ->search(null) + ->offset(null) + ->limit(null); + + $site = Craft::$app->getSites()->getSiteById($elementQuery->siteId); // Fire a 'beforeSearch' event if ($this->hasEventHandlers(self::EVENT_BEFORE_SEARCH)) { $this->trigger(self::EVENT_BEFORE_SEARCH, new SearchEvent([ 'elementQuery' => $elementQuery, - 'elementIds' => $elementIds, 'query' => $searchQuery, - 'siteId' => $siteId, + 'siteId' => $elementQuery->siteId, ])); } @@ -224,7 +136,7 @@ private function _searchElements(?ElementQuery $elementQuery, ?array $elementIds // Sort found elementIds by score arsort($scoresByElementId); - } catch (BadRequest400Exception $e) { + } catch (BadRequest400Exception) { $scoresByElementId = []; } @@ -232,9 +144,8 @@ private function _searchElements(?ElementQuery $elementQuery, ?array $elementIds if ($this->hasEventHandlers(self::EVENT_AFTER_SEARCH)) { $this->trigger(self::EVENT_AFTER_SEARCH, new SearchEvent([ 'elementQuery' => $elementQuery, - 'elementIds' => array_keys($scoresByElementId), 'query' => $searchQuery, - 'siteId' => $siteId, + 'siteId' => $elementQuery->siteId, 'results' => array_keys($scoresByElementId), ])); } @@ -243,11 +154,9 @@ private function _searchElements(?ElementQuery $elementQuery, ?array $elementIds } /** - * Deletes any search indexes that belong to elements that don’t exist anymore. - * - * @since 3.2.10 + * @inheritDoc */ - public function deleteOrphanedIndexes() + public function deleteOrphanedIndexes(): void { Craft::$app->getQueue()->push(new DeleteOrphanedIndexes()); } diff --git a/src/utilities/IndexUtility.php b/src/utilities/IndexUtility.php index 56a7ac0..83a68e3 100644 --- a/src/utilities/IndexUtility.php +++ b/src/utilities/IndexUtility.php @@ -28,7 +28,7 @@ public static function id(): string /** * @inheritdoc */ - public static function iconPath() + public static function iconPath(): ?string { return Craft::getAlias('@codemonauts/elastic/icon-mask.svg'); } @@ -53,7 +53,7 @@ public static function contentHtml(): string 'elements' => $stats['indices'][$indexName]['total']['docs']['count'], 'storage' => $stats['indices'][$indexName]['total']['store']['size_in_bytes'], ]; - } catch (Missing404Exception $e) { + } catch (Missing404Exception) { $indexStatus[] = [ 'site' => $site, 'alias' => 'N/A',