diff --git a/CHANGELOG.md b/CHANGELOG.md index 501e3a02a85..9bba1754997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Unreleased +- Added `craft\elements\db\NestedElementQueryInterface`. - Fixed a bug where uninstalled/missing plugins weren’t getting status indicators on the Plugins index page. +- Fixed errors that occurred when working with nested entries for a newly-added site. ([#15898](https://github.com/craftcms/cms/pull/15898)) ## 5.4.8 - 2024-10-15 diff --git a/src/base/Element.php b/src/base/Element.php index 604c4ac550a..cbb0af6e17d 100644 --- a/src/base/Element.php +++ b/src/base/Element.php @@ -28,6 +28,7 @@ use craft\elements\db\EagerLoadPlan; use craft\elements\db\ElementQuery; use craft\elements\db\ElementQueryInterface; +use craft\elements\db\NestedElementQueryInterface; use craft\elements\ElementCollection; use craft\elements\exporters\Expanded; use craft\elements\exporters\Raw; @@ -2881,16 +2882,21 @@ public function getCanonical(bool $anySite = false): ElementInterface $prop = $anySite ? '_canonicalAnySite' : '_canonical'; if (!isset($this->$prop)) { - $this->$prop = static::find() - ->id($this->_canonicalId) - ->siteId($anySite ? '*' : $this->siteId) - ->preferSites([$this->siteId]) - ->structureId($this->structureId) - ->unique() - ->status(null) - ->trashed(null) - ->ignorePlaceholders() - ->one() ?? false; + $query = static::find() + ->id($this->_canonicalId) + ->siteId($anySite ? '*' : $this->siteId) + ->preferSites([$this->siteId]) + ->structureId($this->structureId) + ->unique() + ->status(null) + ->trashed(null) + ->ignorePlaceholders(); + + if ($this instanceof NestedElementInterface && $query instanceof NestedElementQueryInterface) { + $query->ownerId($this->getOwnerId()); + } + + $this->$prop = $query->one(); } return $this->$prop ?: $this; @@ -3205,7 +3211,7 @@ public function getLink(): ?Markup public function getCrumbs(): array { if ($this instanceof NestedElementInterface) { - $owner = $this->getPrimaryOwner(); + $owner = $this->getOwner(); if ($owner) { return [ ...$owner->getCrumbs(), @@ -3626,6 +3632,7 @@ protected function safeActionMenuItems(): array 'draftId' => $this->isProvisionalDraft ? null : $this->draftId, 'revisionId' => $this->revisionId, 'siteId' => $this->siteId, + 'ownerId' => $this instanceof NestedElementInterface ? $this->getOwnerId() : null, ], ]); } diff --git a/src/controllers/AppController.php b/src/controllers/AppController.php index cfce3b86caf..686d5b71ed4 100644 --- a/src/controllers/AppController.php +++ b/src/controllers/AppController.php @@ -12,6 +12,7 @@ use craft\base\ElementInterface; use craft\base\Iconic; use craft\base\UtilityInterface; +use craft\elements\db\NestedElementQueryInterface; use craft\enums\CmsEdition; use craft\enums\LicenseKeyStatus; use craft\errors\BusyResourceException; @@ -761,6 +762,7 @@ public function actionRenderElements(): Response /** @var string|ElementInterface $elementType */ $elementType = $criterion['type']; $id = $criterion['id']; + $ownerId = $criterion['ownerId'] ?? null; $siteId = $criterion['siteId']; $instances = $criterion['instances']; @@ -768,14 +770,19 @@ public function actionRenderElements(): Response throw new BadRequestHttpException('Invalid element ID'); } - $elements = $elementType::find() + $query = $elementType::find() ->id($id) ->fixedOrder() ->drafts(null) ->revisions(null) ->siteId($siteId) - ->status(null) - ->all(); + ->status(null); + + if ($query instanceof NestedElementQueryInterface) { + $query->ownerId($ownerId); + } + + $elements = $query->all(); // See if there are any provisional drafts we should swap these out with ElementHelper::swapInProvisionalDrafts($elements); diff --git a/src/controllers/ElementsController.php b/src/controllers/ElementsController.php index 9ba655bb4b7..b2be9d36a0f 100644 --- a/src/controllers/ElementsController.php +++ b/src/controllers/ElementsController.php @@ -14,6 +14,8 @@ use craft\base\NestedElementInterface; use craft\behaviors\DraftBehavior; use craft\behaviors\RevisionBehavior; +use craft\elements\db\ElementQueryInterface; +use craft\elements\db\NestedElementQueryInterface; use craft\elements\User; use craft\enums\MenuItemType; use craft\errors\InvalidElementException; @@ -72,6 +74,7 @@ class ElementsController extends Controller private ?string $_elementUid = null; private ?int $_draftId = null; private ?int $_revisionId = null; + private ?int $_ownerId = null; private ?int $_siteId = null; private ?bool $_enabled = null; @@ -113,6 +116,7 @@ public function beforeAction($action): bool $this->_elementUid = $this->_param('elementUid'); $this->_draftId = $this->_param('draftId'); $this->_revisionId = $this->_param('revisionId'); + $this->_ownerId = $this->_param('ownerId'); $this->_siteId = $this->_param('siteId'); $this->_enabled = $this->_param('enabled', $this->_param('setEnabled', true) ? true : null); $this->_enabledForSite = $this->_param('enabledForSite'); @@ -427,6 +431,7 @@ public function actionEdit(?ElementInterface $element, ?int $elementId = null): 'previewToken' => $previewTargets ? $security->generateRandomString() : null, 'previewParamValue' => $previewTargets ? $security->hashData(StringHelper::randomString(10)) : null, 'revisionId' => $element->revisionId, + 'ownerId' => $element instanceof NestedElementInterface ? $element->getOwnerId() : null, 'siteId' => $element->siteId, 'siteStatuses' => $siteStatuses, 'siteToken' => (!Craft::$app->getIsLive() || !$element->getSite()->enabled) ? $security->hashData((string)$element->siteId) : null, @@ -2105,7 +2110,7 @@ private function _element( // Loading an existing element? if ($this->_draftId || $this->_revisionId) { - $element = $elementType::find() + $element = $this->_elementQuery($elementType) ->draftId($this->_draftId) ->revisionId($this->_revisionId) ->provisionalDrafts($this->_provisional) @@ -2156,7 +2161,7 @@ private function _elementById( if ($elementId) { // First check for a provisional draft, if we're open to it if ($checkForProvisionalDraft) { - $element = $elementType::find() + $element = $this->_elementQuery($elementType) ->provisionalDrafts() ->draftOf($elementId) ->draftCreator($user) @@ -2171,7 +2176,7 @@ private function _elementById( } } - $element = $elementType::find() + $element = $this->_elementQuery($elementType) ->id($elementId) ->siteId($siteId) ->preferSites($preferSites) @@ -2185,7 +2190,7 @@ private function _elementById( // finally, check for an unpublished draft // (see https://github.com/craftcms/cms/issues/14199) - return $elementType::find() + return $this->_elementQuery($elementType) ->id($elementId) ->siteId($siteId) ->preferSites($preferSites) @@ -2196,7 +2201,7 @@ private function _elementById( } if ($elementUid) { - return $elementType::find() + return $this->_elementQuery($elementType) ->uid($elementUid) ->siteId($siteId) ->preferSites($preferSites) @@ -2208,6 +2213,16 @@ private function _elementById( return null; } + private function _elementQuery(string $elementType): ElementQueryInterface + { + /** @var string|ElementInterface $elementType */ + $query = $elementType::find(); + if ($query instanceof NestedElementQueryInterface) { + $query->ownerId($this->_ownerId); + } + return $query; + } + /** * Creates a new element. * diff --git a/src/elements/Entry.php b/src/elements/Entry.php index 2e80cd4af11..97f793f4346 100644 --- a/src/elements/Entry.php +++ b/src/elements/Entry.php @@ -1626,7 +1626,7 @@ public function createAnother(): ?self 'sectionId' => $this->sectionId, 'fieldId' => $this->fieldId, 'primaryOwnerId' => $this->getPrimaryOwnerId(), - 'ownerId' => $this->getPrimaryOwnerId(), + 'ownerId' => $this->getOwnerId(), 'sortOrder' => null, 'typeId' => $this->typeId, 'siteId' => $this->siteId, diff --git a/src/elements/db/AddressQuery.php b/src/elements/db/AddressQuery.php index c4619c416c6..94d2d71db9a 100644 --- a/src/elements/db/AddressQuery.php +++ b/src/elements/db/AddressQuery.php @@ -35,7 +35,7 @@ * @replace {myElement} myAddress * @replace {element-class} \craft\elements\Address */ -class AddressQuery extends ElementQuery +class AddressQuery extends ElementQuery implements NestedElementQueryInterface { /** * @var mixed The field ID(s) that the resulting addresses must belong to. @@ -909,34 +909,7 @@ public function lastName(?string $value): static /** - * Narrows the query results based on the field the addresses are contained by. - * - * Possible values include: - * - * | Value | Fetches {elements}… - * | - | - - * | `'foo'` | in a field with a handle of `foo`. - * | `['foo', 'bar']` | in a field with a handle of `foo` or `bar`. - * | an [[craft\fields\Addresses]] object | in a field represented by the object. - * - * --- - * - * ```twig - * {# Fetch {elements} in the Foo field #} - * {% set {elements-var} = {twig-method} - * .field('foo') - * .all() %} - * ``` - * - * ```php - * // Fetch {elements} in the Foo field - * ${elements-var} = {php-method} - * ->field('foo') - * ->all(); - * ``` - * - * @param mixed $value The property value - * @return static self reference + * @inheritdoc * @uses $fieldId * @since 5.0.0 */ @@ -957,35 +930,7 @@ public function field(mixed $value): static } /** - * Narrows the query results based on the field the addresses are contained by, per the fields’ IDs. - * - * Possible values include: - * - * | Value | Fetches addresses… - * | - | - - * | `1` | in a field with an ID of 1. - * | `'not 1'` | not in a field with an ID of 1. - * | `[1, 2]` | in a field with an ID of 1 or 2. - * | `['not', 1, 2]` | not in a field with an ID of 1 or 2. - * - * --- - * - * ```twig - * {# Fetch addresses in the field with an ID of 1 #} - * {% set {elements-var} = {twig-method} - * .fieldId(1) - * .all() %} - * ``` - * - * ```php - * // Fetch addresses in the field with an ID of 1 - * ${elements-var} = {php-method} - * ->fieldId(1) - * ->all(); - * ``` - * - * @param mixed $value The property value - * @return static self reference + * @inheritdoc * @uses $fieldId * @since 5.0.0 */ @@ -996,33 +941,7 @@ public function fieldId(mixed $value): static } /** - * Narrows the query results based on the primary owner element of the addresses, per the owners’ IDs. - * - * Possible values include: - * - * | Value | Fetches addresses… - * | - | - - * | `1` | created for an element with an ID of 1. - * | `[1, 2]` | created for an element with an ID of 1 or 2. - * - * --- - * - * ```twig - * {# Fetch addresses created for an element with an ID of 1 #} - * {% set {elements-var} = {twig-method} - * .primaryOwnerId(1) - * .all() %} - * ``` - * - * ```php - * // Fetch addresses created for an element with an ID of 1 - * ${elements-var} = {php-method} - * ->primaryOwnerId(1) - * ->all(); - * ``` - * - * @param mixed $value The property value - * @return static self reference + * @inheritdoc * @uses $primaryOwnerId * @since 5.0.0 */ @@ -1033,26 +952,7 @@ public function primaryOwnerId(mixed $value): static } /** - * Sets the [[primaryOwnerId()]] and [[siteId()]] parameters based on a given element. - * - * --- - * - * ```twig - * {# Fetch addresses created for this entry #} - * {% set {elements-var} = {twig-method} - * .primaryOwner(myEntry) - * .all() %} - * ``` - * - * ```php - * // Fetch addresses created for this entry - * ${elements-var} = {php-method} - * ->primaryOwner($myEntry) - * ->all(); - * ``` - * - * @param ElementInterface $primaryOwner The primary owner element - * @return static self reference + * @inheritdoc * @uses $primaryOwnerId * @since 5.0.0 */ @@ -1064,25 +964,7 @@ public function primaryOwner(ElementInterface $primaryOwner): static } /** - * Sets the [[ownerId()]] parameter based on a given owner element. - * - * --- - * - * ```twig - * {# Fetch addresses for the current user #} - * {% set {elements-var} = {twig-method} - * .owner(currentUser) - * .all() %} - * ``` - * - * ```php - * // Fetch addresses created for the current user - * ${elements-var} = {php-method} - * ->owner(Craft::$app->user->identity) - * ->all(); - * ``` - * - * @param ElementInterface $owner The owner element + * @inheritdoc * @return static self reference * @uses $ownerId */ @@ -1094,36 +976,11 @@ public function owner(ElementInterface $owner): static } /** - * Narrows the query results based on the addresses’ owner elements, per their IDs. - * - * Possible values include: - * - * | Value | Fetches addresses… - * | - | - - * | `1` | created for an element with an ID of 1. - * | `[1, 2]` | created for an element with an ID of 1 or 2. - * - * --- - * - * ```twig - * {# Fetch addresses created for an element with an ID of 1 #} - * {% set {elements-var} = {twig-method} - * .ownerId(1) - * .all() %} - * ``` - * - * ```php - * // Fetch addresses created for an element with an ID of 1 - * ${elements-var} = {php-method} - * ->ownerId(1) - * ->all(); - * ``` - * - * @param int|int[]|null $value The property value + * @inheritdoc * @return static self reference * @uses $ownerId */ - public function ownerId(array|int|null $value): static + public function ownerId(mixed $value): static { $this->ownerId = $value; $this->_owner = null; @@ -1131,17 +988,7 @@ public function ownerId(array|int|null $value): static } /** - * Narrows the query results based on whether the addresses’ owners are drafts. - * - * Possible values include: - * - * | Value | Fetches addresses… - * | - | - - * | `true` | which can belong to a draft. - * | `false` | which cannot belong to a draft. - * - * @param bool|null $value The property value - * @return static self reference + * @inheritdoc * @uses $allowOwnerDrafts * @since 5.0.0 */ @@ -1152,17 +999,7 @@ public function allowOwnerDrafts(?bool $value = true): static } /** - * Narrows the query results based on whether the addresses’ owners are revisions. - * - * Possible values include: - * - * | Value | Fetches addresses… - * | - | - - * | `true` | which can belong to a revision. - * | `false` | which cannot belong to a revision. - * - * @param bool|null $value The property value - * @return static self reference + * @inheritdoc * @uses $allowOwnerRevisions * @since 5.0.0 */ diff --git a/src/elements/db/EntryQuery.php b/src/elements/db/EntryQuery.php index f437de62986..3e4e58b8184 100644 --- a/src/elements/db/EntryQuery.php +++ b/src/elements/db/EntryQuery.php @@ -53,7 +53,7 @@ * @replace {myElement} myEntry * @replace {element-class} \craft\elements\Entry */ -class EntryQuery extends ElementQuery +class EntryQuery extends ElementQuery implements NestedElementQueryInterface { // General parameters // ------------------------------------------------------------------------- @@ -438,34 +438,7 @@ public function sectionId(mixed $value): static } /** - * Narrows the query results based on the field the entries are contained by. - * - * Possible values include: - * - * | Value | Fetches {elements}… - * | - | - - * | `'foo'` | in a field with a handle of `foo`. - * | `['foo', 'bar']` | in a field with a handle of `foo` or `bar`. - * | a [[craft\fields\Matrix]] object | in a field represented by the object. - * - * --- - * - * ```twig - * {# Fetch {elements} in the Foo field #} - * {% set {elements-var} = {twig-method} - * .field('foo') - * .all() %} - * ``` - * - * ```php - * // Fetch {elements} in the Foo field - * ${elements-var} = {php-method} - * ->field('foo') - * ->all(); - * ``` - * - * @param mixed $value The property value - * @return static self reference + * @inheritdoc * @uses $fieldId * @since 5.0.0 */ @@ -486,35 +459,7 @@ public function field(mixed $value): static } /** - * Narrows the query results based on the field the entries are contained by, per the fields’ IDs. - * - * Possible values include: - * - * | Value | Fetches entries… - * | - | - - * | `1` | in a field with an ID of 1. - * | `'not 1'` | not in a field with an ID of 1. - * | `[1, 2]` | in a field with an ID of 1 or 2. - * | `['not', 1, 2]` | not in a field with an ID of 1 or 2. - * - * --- - * - * ```twig - * {# Fetch entries in the field with an ID of 1 #} - * {% set {elements-var} = {twig-method} - * .fieldId(1) - * .all() %} - * ``` - * - * ```php - * // Fetch entries in the field with an ID of 1 - * ${elements-var} = {php-method} - * ->fieldId(1) - * ->all(); - * ``` - * - * @param mixed $value The property value - * @return static self reference + * @inheritdoc * @uses $fieldId * @since 5.0.0 */ @@ -525,33 +470,7 @@ public function fieldId(mixed $value): static } /** - * Narrows the query results based on the primary owner element of the entries, per the owners’ IDs. - * - * Possible values include: - * - * | Value | Fetches entries… - * | - | - - * | `1` | created for an element with an ID of 1. - * | `[1, 2]` | created for an element with an ID of 1 or 2. - * - * --- - * - * ```twig - * {# Fetch entries created for an element with an ID of 1 #} - * {% set {elements-var} = {twig-method} - * .primaryOwnerId(1) - * .all() %} - * ``` - * - * ```php - * // Fetch entries created for an element with an ID of 1 - * ${elements-var} = {php-method} - * ->primaryOwnerId(1) - * ->all(); - * ``` - * - * @param mixed $value The property value - * @return static self reference + * @inheritdoc * @uses $primaryOwnerId * @since 5.0.0 */ @@ -562,26 +481,7 @@ public function primaryOwnerId(mixed $value): static } /** - * Sets the [[primaryOwnerId()]] and [[siteId()]] parameters based on a given element. - * - * --- - * - * ```twig - * {# Fetch entries created for this entry #} - * {% set {elements-var} = {twig-method} - * .primaryOwner(myEntry) - * .all() %} - * ``` - * - * ```php - * // Fetch entries created for this entry - * ${elements-var} = {php-method} - * ->primaryOwner($myEntry) - * ->all(); - * ``` - * - * @param ElementInterface $primaryOwner The primary owner element - * @return static self reference + * @inheritdoc * @uses $primaryOwnerId * @since 5.0.0 */ @@ -593,33 +493,7 @@ public function primaryOwner(ElementInterface $primaryOwner): static } /** - * Narrows the query results based on the owner element of the entries, per the owners’ IDs. - * - * Possible values include: - * - * | Value | Fetches entries… - * | - | - - * | `1` | created for an element with an ID of 1. - * | `[1, 2]` | created for an element with an ID of 1 or 2. - * - * --- - * - * ```twig - * {# Fetch entries created for an element with an ID of 1 #} - * {% set {elements-var} = {twig-method} - * .ownerId(1) - * .all() %} - * ``` - * - * ```php - * // Fetch entries created for an element with an ID of 1 - * ${elements-var} = {php-method} - * ->ownerId(1) - * ->all(); - * ``` - * - * @param mixed $value The property value - * @return static self reference + * @inheritdoc * @uses $ownerId * @since 5.0.0 */ @@ -630,26 +504,7 @@ public function ownerId(mixed $value): static } /** - * Sets the [[ownerId()]] and [[siteId()]] parameters based on a given element. - * - * --- - * - * ```twig - * {# Fetch entries created for this entry #} - * {% set {elements-var} = {twig-method} - * .owner(myEntry) - * .all() %} - * ``` - * - * ```php - * // Fetch entries created for this entry - * ${elements-var} = {php-method} - * ->owner($myEntry) - * ->all(); - * ``` - * - * @param ElementInterface $owner The owner element - * @return static self reference + * @inheritdoc * @uses $ownerId * @since 5.0.0 */ @@ -661,17 +516,7 @@ public function owner(ElementInterface $owner): static } /** - * Narrows the query results based on whether the entries’ owners are drafts. - * - * Possible values include: - * - * | Value | Fetches entries… - * | - | - - * | `true` | which can belong to a draft. - * | `false` | which cannot belong to a draft. - * - * @param bool|null $value The property value - * @return static self reference + * @inheritdoc * @uses $allowOwnerDrafts * @since 5.0.0 */ @@ -682,17 +527,7 @@ public function allowOwnerDrafts(?bool $value = true): static } /** - * Narrows the query results based on whether the entries’ owners are revisions. - * - * Possible values include: - * - * | Value | Fetches entries… - * | - | - - * | `true` | which can belong to a revision. - * | `false` | which cannot belong to a revision. - * - * @param bool|null $value The property value - * @return static self reference + * @inheritdoc * @uses $allowOwnerRevisions * @since 5.0.0 */ diff --git a/src/elements/db/NestedElementQueryInterface.php b/src/elements/db/NestedElementQueryInterface.php new file mode 100644 index 00000000000..b5c6d7276f6 --- /dev/null +++ b/src/elements/db/NestedElementQueryInterface.php @@ -0,0 +1,225 @@ + + * @since 5.4.9 + */ +interface NestedElementQueryInterface extends ElementQueryInterface +{ + /** + * Narrows the query results based on the field the {elements} are contained by. + * + * Possible values include: + * + * | Value | Fetches {elements}… + * | - | - + * | `'foo'` | in a field with a handle of `foo`. + * | `['foo', 'bar']` | in a field with a handle of `foo` or `bar`. + * | a [[craft\fields\Matrix]] object | in a field represented by the object. + * + * --- + * + * ```twig + * {# Fetch {elements} in the Foo field #} + * {% set {elements-var} = {twig-method} + * .field('foo') + * .all() %} + * ``` + * + * ```php + * // Fetch {elements} in the Foo field + * ${elements-var} = {php-method} + * ->field('foo') + * ->all(); + * ``` + * + * @param mixed $value The property value + * @return static self reference + */ + public function field(mixed $value): static; + + /** + * Narrows the query results based on the field the {elements} are contained by, per the fields’ IDs. + * + * Possible values include: + * + * | Value | Fetches {elements}… + * | - | - + * | `1` | in a field with an ID of 1. + * | `'not 1'` | not in a field with an ID of 1. + * | `[1, 2]` | in a field with an ID of 1 or 2. + * | `['not', 1, 2]` | not in a field with an ID of 1 or 2. + * + * --- + * + * ```twig + * {# Fetch {elements} in the field with an ID of 1 #} + * {% set {elements-var} = {twig-method} + * .fieldId(1) + * .all() %} + * ``` + * + * ```php + * // Fetch {elements} in the field with an ID of 1 + * ${elements-var} = {php-method} + * ->fieldId(1) + * ->all(); + * ``` + * + * @param mixed $value The property value + * @return static self reference + */ + public function fieldId(mixed $value): static; + + /** + * Narrows the query results based on the primary owner element of the {elements}, per the owners’ IDs. + * + * Possible values include: + * + * | Value | Fetches {elements}… + * | - | - + * | `1` | created for an element with an ID of 1. + * | `[1, 2]` | created for an element with an ID of 1 or 2. + * + * --- + * + * ```twig + * {# Fetch {elements} created for an element with an ID of 1 #} + * {% set {elements-var} = {twig-method} + * .primaryOwnerId(1) + * .all() %} + * ``` + * + * ```php + * // Fetch {elements} created for an element with an ID of 1 + * ${elements-var} = {php-method} + * ->primaryOwnerId(1) + * ->all(); + * ``` + * + * @param mixed $value The property value + * @return static self reference + */ + public function primaryOwnerId(mixed $value): static; + + /** + * Sets the [[primaryOwnerId()]] and [[siteId()]] parameters based on a given element. + * + * --- + * + * ```twig + * {# Fetch {elements} created for this entry #} + * {% set {elements-var} = {twig-method} + * .primaryOwner(myEntry) + * .all() %} + * ``` + * + * ```php + * // Fetch {elements} created for this entry + * ${elements-var} = {php-method} + * ->primaryOwner($myEntry) + * ->all(); + * ``` + * + * @param ElementInterface $primaryOwner The primary owner element + * @return static self reference + */ + public function primaryOwner(ElementInterface $primaryOwner): static; + + /** + * Narrows the query results based on the owner element of the {elements}, per the owners’ IDs. + * + * Possible values include: + * + * | Value | Fetches {elements}… + * | - | - + * | `1` | created for an element with an ID of 1. + * | `[1, 2]` | created for an element with an ID of 1 or 2. + * + * --- + * + * ```twig + * {# Fetch {elements} created for an element with an ID of 1 #} + * {% set {elements-var} = {twig-method} + * .ownerId(1) + * .all() %} + * ``` + * + * ```php + * // Fetch {elements} created for an element with an ID of 1 + * ${elements-var} = {php-method} + * ->ownerId(1) + * ->all(); + * ``` + * + * @param mixed $value The property value + * @return static self reference + */ + public function ownerId(mixed $value): static; + + /** + * Sets the [[ownerId()]] and [[siteId()]] parameters based on a given element. + * + * --- + * + * ```twig + * {# Fetch {elements} created for this entry #} + * {% set {elements-var} = {twig-method} + * .owner(myEntry) + * .all() %} + * ``` + * + * ```php + * // Fetch {elements} created for this entry + * ${elements-var} = {php-method} + * ->owner($myEntry) + * ->all(); + * ``` + * + * @param ElementInterface $owner The owner element + * @return static self reference + */ + public function owner(ElementInterface $owner): static; + + /** + * Narrows the query results based on whether the {elements}’ owners are drafts. + * + * Possible values include: + * + * | Value | Fetches {elements}… + * | - | - + * | `true` | which can belong to a draft. + * | `false` | which cannot belong to a draft. + * + * @param bool|null $value The property value + * @return static self reference + */ + public function allowOwnerDrafts(?bool $value = true): static; + + /** + * Narrows the query results based on whether the {elements}’ owners are revisions. + * + * Possible values include: + * + * | Value | Fetches {elements}… + * | - | - + * | `true` | which can belong to a revision. + * | `false` | which cannot belong to a revision. + * + * @param bool|null $value The property value + * @return static self reference + */ + public function allowOwnerRevisions(?bool $value = true): static; +} diff --git a/src/helpers/Cp.php b/src/helpers/Cp.php index 8faea934e36..e7002065a7d 100644 --- a/src/helpers/Cp.php +++ b/src/helpers/Cp.php @@ -18,6 +18,7 @@ use craft\base\FieldLayoutElement; use craft\base\Grippable; use craft\base\Iconic; +use craft\base\NestedElementInterface; use craft\base\Statusable; use craft\base\Thumbable; use craft\behaviors\DraftBehavior; @@ -871,6 +872,7 @@ private static function baseElementAttributes(ElementInterface $element, array $ 'id' => $element->isProvisionalDraft ? $element->getCanonicalId() : $element->id, 'draft-id' => $element->isProvisionalDraft ? null : $element->draftId, 'revision-id' => $element->revisionId, + 'owner-id' => $element instanceof NestedElementInterface ? $element->getOwnerId() : null, 'site-id' => $element->siteId, 'status' => $element->getStatus(), 'label' => (string)$element, diff --git a/src/helpers/ElementHelper.php b/src/helpers/ElementHelper.php index 7141d3124c8..782011aaf5a 100644 --- a/src/helpers/ElementHelper.php +++ b/src/helpers/ElementHelper.php @@ -1035,6 +1035,11 @@ public static function swapInProvisionalDrafts(array &$elements): void $draft->level = $element->level; } + // retain the canonical element's ownerId + if ($element instanceof NestedElementInterface && $draft instanceof NestedElementInterface) { + $draft->setOwnerId($element->getOwnerId()); + } + $elements[$i] = $draft; } } diff --git a/src/web/assets/cp/dist/cp.js b/src/web/assets/cp/dist/cp.js index a39d51f880e..a04f3e88b18 100644 --- a/src/web/assets/cp/dist/cp.js +++ b/src/web/assets/cp/dist/cp.js @@ -1,3 +1,3 @@ /*! For license information please see cp.js.LICENSE.txt */ -(function(){var __webpack_modules__={6322:function(){Craft.Accordion=Garnish.Base.extend({$trigger:null,targetSelector:null,_$target:null,init:function(t){var e=this;this.$trigger=$(t),this.$trigger.data("accordion")&&(console.warn("Double-instantiating an accordion trigger on an element"),this.$trigger.data("accordion").destroy()),this.$trigger.data("accordion",this),this.targetSelector=this.$trigger.attr("aria-controls")?"#".concat(this.$trigger.attr("aria-controls")):null,this.targetSelector&&(this._$target=$(this.targetSelector)),this.addListener(this.$trigger,"click","onTriggerClick"),this.addListener(this.$trigger,"keypress",(function(t){var n=t.keyCode;n!==Garnish.SPACE_KEY&&n!==Garnish.RETURN_KEY||(t.preventDefault(),e.onTriggerClick())}))},onTriggerClick:function(){"true"===this.$trigger.attr("aria-expanded")?this.hideTarget(this._$target):this.showTarget(this._$target)},showTarget:function(t){var e=this;if(t&&t.length){this.showTarget._currentHeight=t.height(),t.removeClass("hidden"),this.$trigger.removeClass("collapsed").addClass("expanded").attr("aria-expanded","true");for(var n=0;n=this.settings.maxItems)){var e=$(t).appendTo(this.$tbody),n=e.find(".delete");this.settings.sortable&&this.sorter.addItems(e),this.$deleteBtns=this.$deleteBtns.add(n),this.addListener(n,"click","handleDeleteBtnClick"),this.totalItems++,this.updateUI()}},reorderItems:function(){var t=this;if(this.settings.sortable){for(var e=[],n=0;n=this.settings.maxItems?$(this.settings.newItemBtnSelector).addClass("hidden"):$(this.settings.newItemBtnSelector).removeClass("hidden"))}},{defaults:{tableSelector:null,noItemsSelector:null,newItemBtnSelector:null,idAttribute:"data-id",nameAttribute:"data-name",sortable:!1,allowDeleteAll:!0,minItems:0,maxItems:null,reorderAction:null,deleteAction:null,reorderSuccessMessage:Craft.t("app","New order saved."),reorderFailMessage:Craft.t("app","Couldn’t save new order."),confirmDeleteMessage:Craft.t("app","Are you sure you want to delete “{name}”?"),deleteSuccessMessage:Craft.t("app","“{name}” deleted."),deleteFailMessage:Craft.t("app","Couldn’t delete “{name}”."),onReorderItems:$.noop,onDeleteItem:$.noop}})},258:function(){Craft.AssetImageEditor=Garnish.Modal.extend({$body:null,$footer:null,$imageTools:null,$buttons:null,$cancelBtn:null,$replaceBtn:null,$saveBtn:null,$focalPointBtn:null,$editorContainer:null,$straighten:null,$croppingCanvas:null,$spinner:null,$constraintContainer:null,$constraintRadioInputs:null,$customConstraints:null,canvas:null,image:null,viewport:null,focalPoint:null,grid:null,croppingCanvas:null,clipper:null,croppingRectangle:null,cropperHandles:null,cropperGrid:null,croppingShade:null,imageStraightenAngle:0,viewportRotation:0,originalWidth:0,originalHeight:0,imageVerticeCoords:null,zoomRatio:1,animationInProgress:!1,currentView:"",assetId:null,cacheBust:null,draggingCropper:!1,scalingCropper:!1,draggingFocal:!1,previousMouseX:0,previousMouseY:0,shiftKeyHeld:!1,editorHeight:0,editorWidth:0,cropperState:!1,scaleFactor:1,flipData:{},focalPointState:!1,maxImageSize:null,lastLoadedDimensions:null,imageIsLoading:!1,mouseMoveEvent:null,croppingConstraint:!1,constraintOrientation:"landscape",showingCustomConstraint:!1,saving:!1,renderImage:null,renderCropper:null,_queue:null,init:function(t,e){var n=this;this._queue=new Craft.Queue,this.cacheBust=Date.now(),this.setSettings(e,Craft.AssetImageEditor.defaults),null===this.settings.allowDegreeFractions&&(this.settings.allowDegreeFractions=Craft.isImagick),Garnish.prefersReducedMotion()&&(this.settings.animationDuration=1),this.assetId=t,this.flipData={x:0,y:0},this.$container=$('').appendTo(Garnish.$bod),this.$body=$('
').appendTo(this.$container),this.$footer=$('