diff --git a/src/config/app.php b/src/config/app.php index e3dc7c433e7..33aac60e4d3 100644 --- a/src/config/app.php +++ b/src/config/app.php @@ -4,7 +4,7 @@ 'id' => 'CraftCMS', 'name' => 'Craft CMS', 'version' => '5.0.0-alpha.3', - 'schemaVersion' => '5.0.0.12', + 'schemaVersion' => '5.0.0.13', 'minVersionRequired' => '4.4.0', 'basePath' => dirname(__DIR__), // Defines the @app alias 'runtimePath' => '@storage/runtime', // Defines the @runtime alias diff --git a/src/migrations/Install.php b/src/migrations/Install.php index 79b954b0625..435b2621344 100644 --- a/src/migrations/Install.php +++ b/src/migrations/Install.php @@ -925,9 +925,10 @@ public function createIndexes(): void 'elementId' => $this->integer()->notNull(), 'attribute' => $this->string(25)->notNull(), 'fieldId' => $this->integer()->notNull(), + 'layoutElementUid' => $this->uid(), 'siteId' => $this->integer()->notNull(), 'keywords' => $this->text()->notNull(), - 'PRIMARY KEY([[elementId]], [[attribute]], [[fieldId]], [[siteId]])', + 'PRIMARY KEY([[elementId]], [[attribute]], [[fieldId]], [[layoutElementUid]], [[siteId]])', ]); $sql = 'CREATE FULLTEXT INDEX ' . @@ -946,10 +947,11 @@ public function createIndexes(): void 'elementId' => $this->integer()->notNull(), 'attribute' => $this->string(25)->notNull(), 'fieldId' => $this->integer()->notNull(), + 'layoutElementUid' => $this->uid(), 'siteId' => $this->integer()->notNull(), 'keywords' => $this->text()->notNull(), 'keywords_vector' => $this->db->getSchema()->createColumnSchemaBuilder('tsvector')->notNull(), - 'PRIMARY KEY([[elementId]], [[attribute]], [[fieldId]], [[siteId]])', + 'PRIMARY KEY([[elementId]], [[attribute]], [[fieldId]], [[layoutElementUid]], [[siteId]])', ]); $sql = 'CREATE INDEX ' . $this->db->quoteTableName($this->db->getIndexName()) . ' ON ' . Table::SEARCHINDEX . ' USING GIN([[keywords_vector]] [[pg_catalog]].[[tsvector_ops]]) WITH (FASTUPDATE=YES)'; diff --git a/src/migrations/m231205_081215_searchindex_amend_pk.php b/src/migrations/m231205_081215_searchindex_amend_pk.php new file mode 100644 index 00000000000..00ff3eecaec --- /dev/null +++ b/src/migrations/m231205_081215_searchindex_amend_pk.php @@ -0,0 +1,41 @@ +db->getIsMysql()) { + $this->dropPrimaryKey('elementId', Table::SEARCHINDEX); + $this->addColumn(Table::SEARCHINDEX, 'layoutElementUid', $this->uid()->after('fieldId')); + $this->addPrimaryKey('layoutUid', Table::SEARCHINDEX, ['elementId', 'attribute', 'fieldId', 'layoutElementUid', 'siteId']); + } else { + $pkName = $this->db->getSchema()->getRawTableName(Table::SEARCHINDEX) . '_pkey'; + + $this->dropPrimaryKey($pkName, Table::SEARCHINDEX); + $this->addColumn(Table::SEARCHINDEX, 'layoutElementUid', $this->uid()->after('fieldId')); + $this->addPrimaryKey($pkName, Table::SEARCHINDEX, ['elementId', 'attribute', 'fieldId', 'layoutElementUid', 'siteId']); + } + + return true; + } + + /** + * @inheritdoc + */ + public function safeDown(): bool + { + echo "m231205_081215_searchindex_amend_pk cannot be reverted.\n"; + return false; + } +} diff --git a/src/services/Search.php b/src/services/Search.php index 7215f66f258..1200b84f85c 100644 --- a/src/services/Search.php +++ b/src/services/Search.php @@ -148,8 +148,8 @@ public function indexElementAttributes(ElementInterface $element, ?array $fieldH // Figure out which fields to update, and which to ignore /** @var FieldInterface[] $updateFields */ $updateFields = []; - /** @var string[] $ignoreFieldIds */ - $ignoreFieldIds = []; + /** @var string[] $ignoreFields */ + $ignoreFields = []; $fieldLayout = $element->getFieldLayout(); if ($fieldLayout !== null) { if ($fieldHandles !== null) { @@ -162,7 +162,7 @@ public function indexElementAttributes(ElementInterface $element, ?array $fieldH $updateFields[] = $field; } else { // Leave its existing keywords alone - $ignoreFieldIds[] = (string)$field->id; + $ignoreFields[] = $field->layoutElement->uid; } } } @@ -173,8 +173,8 @@ public function indexElementAttributes(ElementInterface $element, ?array $fieldH 'elementId' => $element->id, 'siteId' => $element->siteId, ]; - if (!empty($ignoreFieldIds)) { - $deleteCondition = ['and', $deleteCondition, ['not', ['fieldId' => $ignoreFieldIds]]]; + if (!empty($ignoreFields)) { + $deleteCondition = ['and', $deleteCondition, ['not', ['layoutElementUid' => $ignoreFields]]]; } Db::delete(Table::SEARCHINDEX, $deleteCondition); @@ -188,7 +188,7 @@ public function indexElementAttributes(ElementInterface $element, ?array $fieldH foreach ($updateFields as $field) { $fieldValue = $element->getFieldValue($field->handle); $keywords = $field->getSearchKeywords($fieldValue, $element); - $this->_indexKeywords($element, $keywords, fieldId: $field->id); + $this->_indexKeywords($element, $keywords, fieldId: $field->id, layoutElementUid: $field->layoutElement->uid); } // Release the lock @@ -406,8 +406,9 @@ public function deleteOrphanedIndexes(): void * @param string $keywords * @param string|null $attribute * @param int|null $fieldId + * @param string|null $layoutElementUid */ - private function _indexKeywords(ElementInterface $element, string $keywords, ?string $attribute = null, ?int $fieldId = null): void + private function _indexKeywords(ElementInterface $element, string $keywords, ?string $attribute = null, ?int $fieldId = null, ?string $layoutElementUid = null): void { if ($attribute !== null) { $attribute = strtolower($attribute); @@ -422,6 +423,7 @@ private function _indexKeywords(ElementInterface $element, string $keywords, ?st 'element' => $element, 'attribute' => $attribute, 'fieldId' => $fieldId, + 'layoutElementUid' => $layoutElementUid, 'keywords' => $keywords, ]); $this->trigger(self::EVENT_BEFORE_INDEX_KEYWORDS, $event); @@ -438,6 +440,7 @@ private function _indexKeywords(ElementInterface $element, string $keywords, ?st 'elementId' => $element->id, 'attribute' => $attribute ?? 'field', 'fieldId' => $fieldId ? (string)$fieldId : '0', + 'layoutElementUid' => $layoutElementUid ?? '0', 'siteId' => $site->id, ]; @@ -670,9 +673,12 @@ private function _getSqlFromTerm(SearchQueryTerm $term, array|int|null $siteId, // Check for other attributes if ($term->attribute !== null) { // Is attribute a valid fieldId? - $fieldId = $this->_getFieldIdFromAttribute($term->attribute, $customFields); + [$fieldId, $layoutElementUid] = $this->_getFieldIdFromAttribute($term->attribute, $customFields); - if (!empty($fieldId)) { + if (!empty($layoutElementUid)) { + $attr = 'layoutElementUid'; + $val = $layoutElementUid; + } elseif (!empty($fieldId)) { $attr = 'fieldId'; $val = $fieldId; } else { @@ -801,16 +807,36 @@ private function _normalizeTerm(string $term, array|int|null $siteId = null): st * * @param string $attribute * @param MemoizableArray|null $customFields - * @return int|int[]|null + * @return array */ - private function _getFieldIdFromAttribute(string $attribute, ?MemoizableArray $customFields): array|int|null + private function _getFieldIdFromAttribute(string $attribute, ?MemoizableArray $customFields): array { if ($customFields !== null) { - return ArrayHelper::getColumn($customFields->where('handle', $attribute)->all(), 'id'); + return [ + ArrayHelper::getColumn($customFields->where('handle', $attribute)->all(), 'id'), + ArrayHelper::getColumn($customFields->where('handle', $attribute)->all(), 'layoutElement.uid'), + ]; + } + + // we can no longer just get one field by handle; we need to find them all because we now support multiple instanced + $fields = []; + foreach (Craft::$app->getFields()->getAllLayouts() as $fieldLayout) { + array_push($fields, ...$fieldLayout->getCustomFields()); + } + + /** @var FieldInterface[][] $fieldsByHandle */ + $fieldsByHandle = ArrayHelper::index($fields, null, [ + fn(FieldInterface $field) => $field->handle, + ]); + + if (isset($fieldsByHandle[$attribute])) { + return [ + ArrayHelper::getColumn($fieldsByHandle[$attribute], 'id'), + ArrayHelper::getColumn($fieldsByHandle[$attribute], 'layoutElement.uid'), + ]; } - $field = Craft::$app->getFields()->getFieldByHandle($attribute); - return $field->id ?? null; + return [null, null]; } /**