diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index bd6f45a76d6..1162d88fde5 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -448,6 +448,7 @@ - `craft\helpers\StringHelper::toString()` now supports backed enums. - `craft\i18n\I18N::getPrimarySiteLocale()` is now deprecated. `craft\models\Site::getLocale()` should be used instead. - `craft\i18n\I18N::getPrimarySiteLocaleId()` is now deprecated. `craft\models\Site::$language` should be used instead. +- `craft\models\FieldLayout::getField()` and `isFieldIncluded()` now now have a `$filter` argument rather than `$attribute`, and it can be set to a callable. - `craft\models\Section::$propagationMethod` now has a type of `craft\enums\PropagationMethod`. - `craft\services\AssetIndexer::indexFileByListing()` now has a `$volume` argument in place of `$volumeId`. - `craft\services\AssetIndexer::indexFolderByListing()` now has a `$volume` argument in place of `$volumeId`. diff --git a/CHANGELOG.md b/CHANGELOG.md index c0bf70a67db..f86921dfe64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Added `craft\services\Relations::deleteLeftoverRelations()`. ([#13956](https://github.com/craftcms/cms/issues/13956)) - Added `craft\services\Search::shouldCallSearchElements()`. ([#14293](https://github.com/craftcms/cms/issues/14293)) - `craft\behaviors\SessionBehavior::setSuccess()` and `getSuccess()` use the `success` flash key now, rather than `notice`. ([#14345](https://github.com/craftcms/cms/pull/14345)) +- `craft\models\FieldLayout::getField()` and `isFieldIncluded()` now now have a `$filter` argument rather than `$attribute`, and it can be set to a callable. - Exception response data no longer includes an `error` key with the exception message. `message` should be used instead. ([#14346](https://github.com/craftcms/cms/pull/14346)) - Relations for fields that are no longer included in an element’s field layout are now deleted after element save. ([#13956](https://github.com/craftcms/cms/issues/13956)) - Assets fields’ selection modals now open to the last-viewed location by default, if their Default Upload Location doesn’t specify a subpath. ([#14382](https://github.com/craftcms/cms/pull/14382)) @@ -23,6 +24,7 @@ - Fixed a bug where non-global custom fields were being shown on the Fields index page when searching. ([#14344](https://github.com/craftcms/cms/issues/14344)) - Fixed a bug where component slideouts would open when long-pressing on a component’s action menu button or drag handle within a component select input. - Fixed various issues with Tags fields. ([#14356](https://github.com/craftcms/cms/issues/14356), [#14359](https://github.com/craftcms/cms/pull/14359)) +- Fixed a bug where it was possible to include single-instance fields multiple times in a field layout. ([#14352](https://github.com/craftcms/cms/issues/14352)) - Fixed a bug where read/write splitting was always getting disabled for GraphQL POST requests. ([#14324](https://github.com/craftcms/cms/issues/14324)) - Fixed a bug where GraphQL schema edit pages could include empty category headings. - Fixed a bug where asset slideouts weren’t showing validation errors on the Filename field. ([#14329](https://github.com/craftcms/cms/issues/14329)) diff --git a/src/helpers/Cp.php b/src/helpers/Cp.php index 2f27c665b43..cc986771fc2 100644 --- a/src/helpers/Cp.php +++ b/src/helpers/Cp.php @@ -2533,9 +2533,17 @@ private static function _fldFieldSelectorsHtml(string $groupName, array $groupFi private static function _showFldFieldSelector(FieldLayout $fieldLayout, BaseField $field): bool { + $attribute = $field->attribute(); + $uid = $field instanceof CustomField ? $field->getField()->uid : null; + return ( $field->isMultiInstance() || - !$fieldLayout->isFieldIncluded($field->attribute()) + !$fieldLayout->isFieldIncluded(function(BaseField $field) use ($attribute, $uid) { + if ($field instanceof CustomField) { + return $field->getField()->uid === $uid; + } + return $field->attribute() === $attribute; + }) ); } diff --git a/src/models/FieldLayout.php b/src/models/FieldLayout.php index 81dc66da35a..faa4ce5323d 100644 --- a/src/models/FieldLayout.php +++ b/src/models/FieldLayout.php @@ -470,16 +470,16 @@ public function getAvailableUiElements(): array } /** - * Returns whether a field is included in the layout by its attribute. + * Returns whether a field is included in the layout by a callback or its attribute * - * @param string $attribute + * @param callable|string $filter * @return bool * @since 3.5.0 */ - public function isFieldIncluded(string $attribute): bool + public function isFieldIncluded(callable|string $filter): bool { try { - $this->getField($attribute); + $this->getField($filter); return true; } catch (InvalidArgumentException) { return false; @@ -487,24 +487,30 @@ public function isFieldIncluded(string $attribute): bool } /** - * Returns a field that’s included in the layout by its attribute. + * Returns a field that’s included in the layout by a callback or its attribute name. * - * @param string $attribute + * @param callable|string $filter * @return BaseField * @throws InvalidArgumentException if the field isn’t included * @since 3.5.0 */ - public function getField(string $attribute): BaseField + public function getField(callable|string $filter): BaseField { - $filter = fn(FieldLayoutElement $layoutElement) => ( - $layoutElement instanceof BaseField && - $layoutElement->attribute() === $attribute - ); + if (is_string($filter)) { + $attribute = $filter; + $filter = fn(BaseField $field) => $field->attribute() === $attribute; + } + /** @var BaseField|null $field */ - $field = $this->_element($filter); + $field = $this->_element(fn(FieldLayoutElement $layoutElement) => ( + $layoutElement instanceof BaseField && + $filter($layoutElement) + )); + if (!$field) { - throw new InvalidArgumentException("Invalid field: $attribute"); + throw new InvalidArgumentException(isset($attribute) ? "Invalid field: $attribute" : 'Invalid field'); } + return $field; }