diff --git a/src/gql/arguments/elements/Entry.php b/src/gql/arguments/elements/Entry.php index d4a2c163410..60a05dec7cc 100644 --- a/src/gql/arguments/elements/Entry.php +++ b/src/gql/arguments/elements/Entry.php @@ -43,6 +43,26 @@ public static function getArguments(): array 'type' => Type::listOf(QueryArgument::getType()), 'description' => 'Narrows the query results based on the sections the entries belong to, per the sections’ IDs.', ], + 'field' => [ + 'name' => 'field', + 'type' => Type::listOf(Type::string()), + 'description' => 'Narrows the query results based on the field the entries are contained by.', + ], + 'fieldId' => [ + 'name' => 'fieldId', + 'type' => Type::listOf(QueryArgument::getType()), + 'description' => 'Narrows the query results based on the field the entries are contained by, per the fields’ IDs.', + ], + 'primaryOwnerId' => [ + 'name' => 'primaryOwnerId', + 'type' => Type::listOf(QueryArgument::getType()), + 'description' => 'Narrows the query results based on the primary owner element of the entries, per the owners’ IDs.', + ], + 'ownerId' => [ + 'name' => 'ownerId', + 'type' => Type::listOf(QueryArgument::getType()), + 'description' => 'Narrows the query results based on the owner element of the entries, per the owners’ IDs.', + ], 'type' => [ 'name' => 'type', 'type' => Type::listOf(Type::string()), diff --git a/src/gql/arguments/mutations/NestedEntry.php b/src/gql/arguments/mutations/NestedEntry.php new file mode 100644 index 00000000000..369e2f90726 --- /dev/null +++ b/src/gql/arguments/mutations/NestedEntry.php @@ -0,0 +1,38 @@ + + * @since 5.0.0 + */ +class NestedEntry extends Entry +{ + /** + * @inheritdoc + */ + public static function getArguments(): array + { + return array_merge(parent::getArguments(), [ + 'ownerId' => [ + 'name' => 'ownerId', + 'type' => Type::id(), + 'description' => 'The entry’s owner ID.', + ], + 'sortOrder' => [ + 'name' => 'sortOrder', + 'type' => Type::int(), + 'description' => 'The entry’s sort order.', + ], + ]); + } +} diff --git a/src/gql/interfaces/elements/Entry.php b/src/gql/interfaces/elements/Entry.php index 31828396854..52469a9904e 100644 --- a/src/gql/interfaces/elements/Entry.php +++ b/src/gql/interfaces/elements/Entry.php @@ -94,15 +94,36 @@ public static function getFieldDefinitions(): array ], 'sectionId' => [ 'name' => 'sectionId', - 'type' => Type::nonNull(Type::int()), + 'type' => Type::int(), 'description' => 'The ID of the section that contains the entry.', ], 'sectionHandle' => [ 'name' => 'sectionHandle', - 'type' => Type::nonNull(Type::string()), + 'type' => Type::string(), 'description' => 'The handle of the section that contains the entry.', 'complexity' => Gql::singleQueryComplexity(), ], + 'fieldId' => [ + 'name' => 'fieldId', + 'type' => Type::int(), + 'description' => 'The ID of the field that contains the entry.', + ], + 'fieldHandle' => [ + 'name' => 'fieldHandle', + 'type' => Type::string(), + 'description' => 'The handle of the field that contains the entry.', + 'complexity' => Gql::singleQueryComplexity(), + ], + 'ownerId' => [ + 'name' => 'ownerId', + 'type' => Type::int(), + 'description' => 'The ID of the entry’s owner elementt.', + ], + 'sortOrder' => [ + 'name' => 'sortOrder', + 'type' => Type::int(), + 'description' => 'The entry’s position within the field that contains it.', + ], 'typeId' => [ 'name' => 'typeId', 'type' => Type::nonNull(Type::int()), diff --git a/src/gql/mutations/Entry.php b/src/gql/mutations/Entry.php index 19a621cdbe3..d38d005f9e2 100644 --- a/src/gql/mutations/Entry.php +++ b/src/gql/mutations/Entry.php @@ -8,8 +8,10 @@ namespace craft\gql\mutations; use Craft; +use craft\base\ElementContainerFieldInterface; use craft\gql\arguments\mutations\Draft as DraftMutationArguments; use craft\gql\arguments\mutations\Entry as EntryMutationArguments; +use craft\gql\arguments\mutations\NestedEntry; use craft\gql\arguments\mutations\Structure as StructureArguments; use craft\gql\base\ElementMutationResolver; use craft\gql\base\Mutation; @@ -62,6 +64,35 @@ public static function getMutations(): array } } + $fieldsService = Craft::$app->getFields(); + foreach ($fieldsService->getNestedEntryFieldTypes() as $type) { + foreach ($fieldsService->getFieldsByType($type) as $field) { + /** @var ElementContainerFieldInterface $field */ + $scope = "nestedentryfields.$field->uid"; + $canCreate = Gql::canSchema($scope, 'create'); + $canSave = Gql::canSchema($scope, 'save'); + + if ($canCreate || $canSave) { + // Create a mutation for each entry type + foreach ($field->getFieldLayoutProviders() as $provider) { + if ($provider instanceof EntryTypeModel) { + foreach (static::createSaveMutationsForField($field, $provider, $canSave) as $mutation) { + $mutationList[$mutation['name']] = $mutation; + } + } + } + } + + if (!$createDraftMutations && $canSave) { + $createDraftMutations = true; + } + + if (!$createDeleteMutation && Gql::canSchema($scope, 'delete')) { + $createDeleteMutation = true; + } + } + } + if ($createDeleteMutation || $createDraftMutations) { $resolver = Craft::createObject(EntryMutationResolver::class); @@ -205,4 +236,60 @@ public static function createSaveMutations( return $mutations; } + + /** + * Create the per-entry-type save mutations for a nested entry field. + * + * @param ElementContainerFieldInterface $field + * @param EntryTypeModel $entryType + * @param bool $createSaveDraftMutation + * @return array + * @throws InvalidConfigException + */ + public static function createSaveMutationsForField( + ElementContainerFieldInterface $field, + EntryTypeModel $entryType, + bool $createSaveDraftMutation, + ): array { + $mutations = []; + + $entryMutationArguments = NestedEntry::getArguments(); + $draftMutationArguments = DraftMutationArguments::getArguments(); + $generatedType = EntryType::generateType($entryType); + + /** @var EntryMutationResolver $resolver */ + $resolver = Craft::createObject(EntryMutationResolver::class); + $resolver->setResolutionData('entryType', $entryType); + $resolver->setResolutionData('field', $field); + + static::prepareResolver($resolver, $entryType->getCustomFields()); + + $description = sprintf('Save a “%s” entry in the “%s” %s field.', $entryType->name, $field->name, $field::displayName()); + $draftDescription = sprintf('Save a “%s” entry draft in the “%s” %s field.', $entryType->name, $field->name, $field::displayName()); + + $contentFields = $resolver->getResolutionData(ElementMutationResolver::CONTENT_FIELD_KEY); + $entryMutationArguments = array_merge($entryMutationArguments, $contentFields); + $draftMutationArguments = array_merge($draftMutationArguments, $contentFields); + + $mutations[] = [ + 'name' => "save_{$field->handle}Field_{$entryType->handle}_Entry", + 'description' => $description, + 'args' => $entryMutationArguments, + 'resolve' => [$resolver, 'saveEntry'], + 'type' => $generatedType, + ]; + + // This gets created only if allowed to save entries + if ($createSaveDraftMutation) { + $mutations[] = [ + 'name' => "save_{$field->handle}_{$entryType->handle}_Draft", + 'description' => $draftDescription, + 'args' => $draftMutationArguments, + 'resolve' => [$resolver, 'saveEntry'], + 'type' => $generatedType, + ]; + } + + return $mutations; + } } diff --git a/src/gql/queries/Entry.php b/src/gql/queries/Entry.php index 610c6aa6996..f8413dc0918 100644 --- a/src/gql/queries/Entry.php +++ b/src/gql/queries/Entry.php @@ -12,6 +12,7 @@ use craft\gql\GqlEntityRegistry; use craft\gql\interfaces\elements\Entry as EntryInterface; use craft\gql\resolvers\elements\Entry as EntryResolver; +use craft\gql\types\elements\Entry as EntryGqlType; use craft\gql\types\generators\EntryType as EntryTypeGenerator; use craft\helpers\ArrayHelper; use craft\helpers\Gql as GqlHelper; @@ -37,6 +38,15 @@ public static function getQueries(bool $checkToken = true): array return []; } + /** @var EntryGqlType[] $entryTypeGqlTypes */ + $entryTypeGqlTypes = array_map( + fn(EntryType $entryType) => EntryTypeGenerator::generateType($entryType), + ArrayHelper::index( + GqlHelper::getSchemaContainedEntryTypes(), + fn(EntryType $entryType) => $entryType->id + ), + ); + return [ 'entries' => [ 'type' => Type::listOf(EntryInterface::getType()), @@ -59,26 +69,20 @@ public static function getQueries(bool $checkToken = true): array 'description' => 'This query is used to query for a single entry.', 'complexity' => GqlHelper::singleQueryComplexity(), ], - ...self::getSectionLevelFields(), + ...self::sectionLevelFields($entryTypeGqlTypes), + ...self::nestedEntryFieldLevelFields($entryTypeGqlTypes), ]; } /** * Return the query fields for section level queries. * + * @param EntryGqlType[] $entryTypeGqlTypes * @return array * @throws InvalidConfigException */ - protected static function getSectionLevelFields(): array + private static function sectionLevelFields(array $entryTypeGqlTypes): array { - $entryTypeGqlTypes = array_map( - fn(EntryType $entryType) => EntryTypeGenerator::generateType($entryType), - ArrayHelper::index( - GqlHelper::getSchemaContainedEntryTypes(), - fn(EntryType $entryType) => $entryType->id - ), - ); - $gqlTypes = []; foreach (GqlHelper::getSchemaContainedSections() as $section) { @@ -101,7 +105,13 @@ protected static function getSectionLevelFields(): array // Unset unusable arguments $arguments = EntryArguments::getArguments(); - unset($arguments['section'], $arguments['sectionId']); + unset( + $arguments['section'], + $arguments['sectionId'], + $arguments['field'], + $arguments['fieldId'], + $arguments['ownerId'], + ); // Create the section query field $sectionQueryType = [ @@ -120,4 +130,60 @@ protected static function getSectionLevelFields(): array return $gqlTypes; } + + /** + * Return the query fields for nested entry field queries. + * + * @param EntryGqlType[] $entryTypeGqlTypes + * @return array + * @throws InvalidConfigException + */ + private static function nestedEntryFieldLevelFields(array $entryTypeGqlTypes): array + { + $gqlTypes = []; + + foreach (GqlHelper::getSchemaContainedNestedEntryFields() as $field) { + $typeName = "{$field->handle}NestedEntriesQuery"; + $fieldQueryType = GqlEntityRegistry::getEntity($typeName); + + if (!$fieldQueryType) { + $entryTypesInField = []; + + // Loop through the entry types and create further queries + foreach ($field->getFieldLayoutProviders() as $provider) { + if ($provider instanceof EntryType && isset($entryTypeGqlTypes[$provider->id])) { + $entryTypesInField[] = $entryTypeGqlTypes[$provider->id]; + } + } + + if (empty($entryTypesInField)) { + continue; + } + + // Unset unusable arguments + $arguments = EntryArguments::getArguments(); + unset( + $arguments['section'], + $arguments['sectionId'], + $arguments['field'], + $arguments['fieldId'], + ); + + // Create the query field + $fieldQueryType = [ + 'name' => "{$field->handle}FieldEntries", + 'args' => $arguments, + 'description' => sprintf('Entries within the “%s” %s field.', $field->name, $field::displayName()), + 'type' => Type::listOf(GqlHelper::getUnionType("{$field->handle}FieldEntryUnion", $entryTypesInField)), + // Enforce the section argument and set the source to `null`, to enforce a new element query. + 'resolve' => fn($source, array $arguments, $context, ResolveInfo $resolveInfo) => + EntryResolver::resolve(null, $arguments + ['field' => $field->handle], $context, $resolveInfo), + ]; + } + + $gqlTypes[$field->handle] = $fieldQueryType; + } + + return $gqlTypes; + } } diff --git a/src/gql/resolvers/elements/Entry.php b/src/gql/resolvers/elements/Entry.php index 61b30788a0e..da245a7131b 100644 --- a/src/gql/resolvers/elements/Entry.php +++ b/src/gql/resolvers/elements/Entry.php @@ -33,20 +33,36 @@ public static function prepareQuery(mixed $source, array $arguments, ?string $fi $query = EntryElement::find(); $pairs = GqlHelper::extractAllowedEntitiesFromSchema('read'); - if (!isset($pairs['sections'])) { + if (!isset($pairs['sections']) && !isset($pairs['nestedentryfields'])) { return ElementCollection::empty(); } - $sectionUids = array_flip($pairs['sections']); - $sectionIds = []; + $condition = ['or']; - foreach (Craft::$app->getEntries()->getAllSections() as $section) { - if (isset($sectionUids[$section->uid])) { - $sectionIds[] = $section->id; + if (isset($pairs['sections'])) { + $entriesService = Craft::$app->getEntries(); + $sectionIds = array_filter(array_map( + fn(string $uid) => $entriesService->getSectionByUid($uid)?->id, + $pairs['sections'], + )); + if (!empty($sectionIds)) { + $condition[] = ['in', 'entries.sectionId', $sectionIds]; } } - $query->andWhere(['in', 'entries.sectionId', $sectionIds]); + if (isset($pairs['nestedentryfields'])) { + $fieldsService = Craft::$app->getFields(); + $types = array_flip($fieldsService->getNestedEntryFieldTypes()); + $fieldIds = array_filter(array_map(function(string $uid) use ($fieldsService, $types) { + $field = $fieldsService->getFieldByUid($uid); + return $field && isset($types[$field::class]) ? $field->id : null; + }, $pairs['nestedentryfields'])); + if (!empty($fieldIds)) { + $condition[] = ['in', 'entries.fieldId', $fieldIds]; + } + } + + $query->andWhere($condition); // If not, get the prepared element query } else { diff --git a/src/gql/resolvers/mutations/Entry.php b/src/gql/resolvers/mutations/Entry.php index 7b4c433f678..8a2adaa4986 100644 --- a/src/gql/resolvers/mutations/Entry.php +++ b/src/gql/resolvers/mutations/Entry.php @@ -9,6 +9,7 @@ use Craft; use craft\base\Element; +use craft\base\ElementContainerFieldInterface; use craft\behaviors\DraftBehavior; use craft\elements\db\EntryQuery; use craft\elements\Entry as EntryElement; @@ -161,7 +162,15 @@ public function createDraft(mixed $source, array $arguments, mixed $context, Res } $section = $entry->getSection(); - $this->requireSchemaAction("sections.$section->uid", 'save'); + $field = $entry->getField(); + + if ($section) { + $this->requireSchemaAction("sections.$section->uid", 'save'); + } elseif ($field) { + $this->requireSchemaAction("nestedentryfields.$field->uid", 'save'); + } else { + throw new Error('Unable to perform the action.'); + } $draftName = $arguments['name'] ?? ''; $draftNotes = $arguments['notes'] ?? ''; @@ -199,7 +208,15 @@ public function publishDraft(mixed $source, array $arguments, mixed $context, Re } $section = $draft->getSection(); - $this->requireSchemaAction("sections.$section->uid", 'save'); + $field = $draft->getField(); + + if ($section) { + $this->requireSchemaAction("sections.$section->uid", 'save'); + } elseif ($field) { + $this->requireSchemaAction("nestedentryfields.$field->uid", 'save'); + } else { + throw new Error('Unable to perform the action.'); + } /** @var EntryElement $draft */ $draft = Craft::$app->getDrafts()->applyDraft($draft); @@ -216,16 +233,30 @@ public function publishDraft(mixed $source, array $arguments, mixed $context, Re */ protected function getEntryElement(array $arguments): EntryElement { - /** @var Section $section */ + /** @var Section|null $section */ $section = $this->getResolutionData('section'); + /** @var ElementContainerFieldInterface|null $field */ + $field = $this->getResolutionData('field'); /** @var EntryType $entryType */ $entryType = $this->getResolutionData('entryType'); // Figure out whether the mutation is about an already saved entry - $canIdentify = $section->type === Section::TYPE_SINGLE || !empty($arguments['id']) || !empty($arguments['uid']) || !empty($arguments['draftId']); + $canIdentify = ( + $section?->type === Section::TYPE_SINGLE || + !empty($arguments['id']) || + !empty($arguments['uid']) || + !empty($arguments['draftId']) + ); // Check if relevant schema is present - $this->requireSchemaAction("sections.$section->uid", $canIdentify ? 'save' : 'create'); + $action = $canIdentify ? 'save' : 'create'; + if ($section) { + $this->requireSchemaAction("sections.$section->uid", $action); + } elseif ($field) { + $this->requireSchemaAction("nestedentryfields.$field->uid", $action); + } else { + throw new Error('Unable to perform the action.'); + } $elementService = Craft::$app->getElements(); @@ -245,12 +276,19 @@ protected function getEntryElement(array $arguments): EntryElement $entry = $elementService->createElement(EntryElement::class); } - // If they are identifying a specific entry, don't allow changing the section ID. - if ($canIdentify && $entry->sectionId !== $section->id) { - throw new Error('Impossible to change the section of an existing entry'); + // If they are identifying a specific entry, don't allow changing the section/field ID. + if ($canIdentify) { + if ($section) { + if ($entry->sectionId !== $section->id) { + throw new Error('Impossible to change the section of an existing entry'); + } + } elseif ($entry->fieldId !== $field->id) { + throw new Error('Impossible to change the field of an existing entry'); + } } - $entry->sectionId = $section->id; + $entry->sectionId = $section?->id; + $entry->fieldId = $field?->id; // Null the field layout ID in case the entry type changes. if ($entry->getTypeId() !== $entryType->id) { @@ -271,18 +309,24 @@ protected function getEntryElement(array $arguments): EntryElement */ protected function identifyEntry(EntryQuery $entryQuery, array $arguments): EntryQuery { - /** @var Section $section */ + /** @var Section|null $section */ $section = $this->getResolutionData('section'); + /** @var ElementContainerFieldInterface|null $field */ + $field = $this->getResolutionData('field'); /** @var EntryType $entryType */ $entryType = $this->getResolutionData('entryType'); + if ($field) { + // nested entries won’t be queried if a field param isn’t set + $entryQuery->fieldId($field->id); + } if (!empty($arguments['draftId'])) { $entryQuery->draftId($arguments['draftId']); if (array_key_exists('provisional', $arguments)) { $entryQuery->provisionalDrafts($arguments['provisional']); } - } elseif ($section->type === Section::TYPE_SINGLE) { + } elseif ($section?->type === Section::TYPE_SINGLE) { $entryQuery->typeId($entryType->id); } elseif (!empty($arguments['uid'])) { $entryQuery->uid($arguments['uid']); diff --git a/src/gql/types/elements/Entry.php b/src/gql/types/elements/Entry.php index 26c073a44d8..bb4007bd669 100644 --- a/src/gql/types/elements/Entry.php +++ b/src/gql/types/elements/Entry.php @@ -42,8 +42,12 @@ protected function resolve(mixed $source, array $arguments, mixed $context, Reso return match ($fieldName) { 'sectionId' => $source->sectionId, + 'fieldId' => $source->fieldId, + 'ownerId' => $source->ownerId, + 'sortOrder' => $source->sortOrder, 'typeId' => $source->getTypeId(), - 'sectionHandle' => $source->getSection()->handle, + 'sectionHandle' => $source->getSection()?->handle, + 'fieldHandle' => $source->getField()?->handle, 'typeHandle' => $source->getType()->handle, 'draftName', 'draftNotes' => $source->getIsDraft() ? $source->{$fieldName} : null, 'draftCreator' => $source->getIsDraft() ? $source->getCreator() : null, diff --git a/src/helpers/Gql.php b/src/helpers/Gql.php index aecd217404c..ebc8ce2481a 100644 --- a/src/helpers/Gql.php +++ b/src/helpers/Gql.php @@ -8,6 +8,7 @@ namespace craft\helpers; use Craft; +use craft\base\ElementContainerFieldInterface; use craft\base\ElementInterface; use craft\errors\GqlException; use craft\gql\base\Directive; @@ -582,6 +583,14 @@ public static function getSchemaContainedEntryTypes(?GqlSchema $schema = null): } } + foreach (static::getSchemaContainedNestedEntryFields($schema) as $field) { + foreach ($field->getFieldLayoutProviders() as $provider) { + if ($provider instanceof EntryType) { + $entryTypes[$provider->uid] = $provider; + } + } + } + return array_values($entryTypes); } @@ -599,6 +608,24 @@ public static function getSchemaContainedSections(?GqlSchema $schema = null): ar ); } + /** + * Returns all nested entry fields a given (or loaded) schema contains. + * + * @return ElementContainerFieldInterface[] + * @since 5.0.0 + */ + public static function getSchemaContainedNestedEntryFields(?GqlSchema $schema = null): array + { + $fieldsService = Craft::$app->getFields(); + /** @var ElementContainerFieldInterface[] $fields */ + $fields = array_merge(...array_map( + fn(string $type) => $fieldsService->getFieldsByType($type), + $fieldsService->getNestedEntryFieldTypes() + )); + return array_filter($fields, fn(ElementContainerFieldInterface $field) => + static::isSchemaAwareOf("nestedentryfields.$field->uid", $schema)); + } + /** * @param GqlSchema|null $schema * @return GqlSchema diff --git a/src/services/Entries.php b/src/services/Entries.php index 2fbdd01938e..40a69f2acbc 100644 --- a/src/services/Entries.php +++ b/src/services/Entries.php @@ -1714,8 +1714,11 @@ protected function getEntryTypeUsages(): array } // Fields - foreach (Craft::$app->getFields()->getAllFields() as $field) { - if ($field instanceof ElementContainerFieldInterface) { + $fieldsService = Craft::$app->getFields(); + foreach ($fieldsService->getNestedEntryFieldTypes() as $type) { + /** @var ElementContainerFieldInterface[] $fields */ + $fields = $fieldsService->getFieldsByType($type); + foreach ($fields as $field) { foreach ($field->getFieldLayoutProviders() as $provider) { if ($provider instanceof EntryType) { $entryTypeUsages[$provider->id][] = [ diff --git a/src/services/Fields.php b/src/services/Fields.php index eda6404001a..ec89c494603 100644 --- a/src/services/Fields.php +++ b/src/services/Fields.php @@ -8,6 +8,7 @@ namespace craft\services; use Craft; +use craft\base\ElementContainerFieldInterface; use craft\base\ElementInterface; use craft\base\Field; use craft\base\FieldInterface; @@ -98,6 +99,15 @@ class Fields extends Component */ public const EVENT_REGISTER_FIELD_TYPES = 'registerFieldTypes'; + /** + * @event RegisterComponentTypesEvent The event that is triggered when registering field types which manage nested entries. + * + * These field types must implement [[ElementContainerFieldInterface]]. + * + * @since 5.0.0 + */ + public const EVENT_REGISTER_NESTED_ENTRY_FIELD_TYPES = 'registerNestedEntryFieldTypes'; + /** * @event DefineCompatibleFieldTypesEvent The event that is triggered when defining the compatible field types for a field. * @see getCompatibleFieldTypes() @@ -298,6 +308,26 @@ public function getCompatibleFieldTypes(FieldInterface $field, bool $includeCurr return $types; } + /** + * Returns all field types which manage nested entries. + * + * @return string[] The field type classes which manage nested entries + * @phpstan-return class-string[] + */ + public function getNestedEntryFieldTypes(): array + { + $fieldTypes = [ + MatrixField::class, + ]; + + $event = new RegisterComponentTypesEvent([ + 'types' => $fieldTypes, + ]); + $this->trigger(self::EVENT_REGISTER_NESTED_ENTRY_FIELD_TYPES, $event); + + return $event->types; + } + /** * Creates a field with a given config. * diff --git a/src/services/Gql.php b/src/services/Gql.php index f6088f39169..d2fc2e35042 100644 --- a/src/services/Gql.php +++ b/src/services/Gql.php @@ -8,6 +8,7 @@ namespace craft\services; use Craft; +use craft\base\ElementContainerFieldInterface; use craft\base\ElementInterface as BaseElementInterface; use craft\base\FieldInterface; use craft\base\GqlInlineFragmentFieldInterface; @@ -1551,6 +1552,56 @@ private function entrySchemaComponents(): array ]; } + // Add components for fields that manage nested entries + $fieldsService = Craft::$app->getFields(); + /** @var ElementContainerFieldInterface[] $fields */ + $fields = array_merge(...array_map( + fn(string $type) => $fieldsService->getFieldsByType($type), + $fieldsService->getNestedEntryFieldTypes(), + )); + usort($fields, fn(ElementContainerFieldInterface $a, ElementContainerFieldInterface $b) => + $a::displayName() <=> $b::displayName()); + + foreach ($fields as $field) { + $name = Craft::t('site', $field->name); + $type = $field::displayName(); + $prefix = "nestedentryfields.$field->uid"; + + $queryComponents["$prefix:read"] = [ + 'label' => Craft::t('app', 'Query for entries in the “{name}” {type} field', [ + 'name' => $name, + 'type' => $type, + ]), + ]; + + $mutationComponents["$prefix:edit"] = [ + 'label' => Craft::t('app', 'Edit entries in the “{name}” {type} field', [ + 'name' => $name, + 'type' => $type, + ]), + 'nested' => [ + "$prefix:create" => [ + 'label' => Craft::t('app', 'Create entries in the “{section}” {type} field', [ + 'name' => $name, + 'type' => $type, + ]), + ], + "$prefix:save" => [ + 'label' => Craft::t('app', 'Save entries in the “{section}” {type} field', [ + 'name' => $name, + 'type' => $type, + ]), + ], + "$prefix:delete" => [ + 'label' => Craft::t('app', 'Delete entries in the “{section}” {type} field', [ + 'name' => $name, + 'type' => $type, + ]), + ], + ], + ]; + } + return [$queryComponents, $mutationComponents]; } diff --git a/src/translations/en/app.php b/src/translations/en/app.php index be591e93f6e..c644b24e556 100644 --- a/src/translations/en/app.php +++ b/src/translations/en/app.php @@ -444,6 +444,7 @@ 'Create and continue editing' => 'Create and continue editing', 'Create assets in the “{volume}” volume' => 'Create assets in the “{volume}” volume', 'Create entries in the “{section}” section' => 'Create entries in the “{section}” section', + 'Create entries in the “{section}” {type} field' => 'Create entries in the “{section}” {type} field', 'Create subfolders' => 'Create subfolders', 'Create your account' => 'Create your account', 'Create {type}' => 'Create {type}', @@ -500,6 +501,7 @@ 'Delete categories from the “{categoryGroup}” category group' => 'Delete categories from the “{categoryGroup}” category group', 'Delete custom source' => 'Delete custom source', 'Delete entries in the “{section}” section' => 'Delete entries in the “{section}” section', + 'Delete entries in the “{section}” {type} field' => 'Delete entries in the “{section}” {type} field', 'Delete folder' => 'Delete folder', 'Delete for site' => 'Delete for site', 'Delete heading' => 'Delete heading', @@ -1210,6 +1212,7 @@ 'Query for element revisions' => 'Query for element revisions', 'Query for elements in the “{site}” site' => 'Query for elements in the “{site}” site', 'Query for entries in the “{name}” section' => 'Query for entries in the “{name}” section', + 'Query for entries in the “{name}” {type} field' => 'Query for entries in the “{name}” {type} field', 'Query for non-enabled elements' => 'Query for non-enabled elements', 'Query for tags in the “{name}” tag group' => 'Query for tags in the “{name}” tag group', 'Query for the “{name}” entry' => 'Query for the “{name}” entry', @@ -1331,6 +1334,7 @@ 'Save assets uploaded by other users' => 'Save assets uploaded by other users', 'Save categories in the “{categoryGroup}” category group' => 'Save categories in the “{categoryGroup}” category group', 'Save entries in the “{section}” section' => 'Save entries in the “{section}” section', + 'Save entries in the “{section}” {type} field' => 'Save entries in the “{section}” {type} field', 'Save entries to all sites enabled for this section' => 'Save entries to all sites enabled for this section', 'Save entries to all sites the owner element is saved in' => 'Save entries to all sites the owner element is saved in', 'Save entries to other sites in the same site group' => 'Save entries to other sites in the same site group',