Skip to content

Commit

Permalink
Merge branch '5.0' into bugfix/taphold-to-drag-dont-open-for-edit
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/web/assets/cp/dist/cp.js
#	src/web/assets/cp/dist/cp.js.map
  • Loading branch information
brandonkelly committed Feb 13, 2024
2 parents b8817bf + 38e51ab commit 30537dd
Show file tree
Hide file tree
Showing 27 changed files with 733 additions and 207 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
- Entry queries now have `field`, `fieldId`, `primaryOwner`, `primaryOwnerId`, `owner`, `ownerId`, `allowOwnerDrafts`, and `allowOwnerRevisions` params.
- Entry queries’ `authorId` params now support passing multiple IDs prefixed with `and`, to fetch entries with multiple listed authors.
- User queries now have an `authorOf` param.
- Nested addresses are now cached by their field ID, and address queries now register cache tags based on their `field` and `fieldId` params.
- Nested entries are now cached by their field ID, and entry queries now register cache tags based on their `field` and `fieldId` params.
- GraphQL schemas can now include queries and mutations for nested entries (e.g. within Matrix or CKEditor fields) directly. ([#14366](https://github.com/craftcms/cms/pull/14366))
- Added the `fieldId`, `fieldHandle`, `ownerId`, and `sortOrder` entry GraphQL fields. ([#14366](https://github.com/craftcms/cms/pull/14366))
- Entries’ GraphQL type names are now formatted as `<entryTypeHandle>_Entry`, and are no longer prefixed with their section’s handle. (That goes for Matrix-nested entries as well.)
- Entries now have `author` and `authorIds` GraphQL field.
- Matrix fields’ GraphQL mutation types now expect nested entries to be defined by an `entries` field rather than `blocks`.
Expand Down Expand Up @@ -357,7 +361,9 @@
- Added `craft\services\Entries::refreshEntryTypes()`.
- Added `craft\services\Entries::saveSection()`.
- Added `craft\services\Fields::$fieldContext`, which replaces `craft\services\Content::$fieldContext`.
- Added `craft\services\Fields::EVENT_REGISTER_NESTED_ENTRY_FIELD_TYPES`.
- Added `craft\services\Fields::getAllLayouts()`.
- Added `craft\services\Fields::getNestedEntryFieldTypes()`.
- Added `craft\services\Gql::defineContentArgumentsForFieldLayouts()`.
- Added `craft\services\Gql::defineContentArgumentsForFields()`.
- Added `craft\services\Gql::getOrSetContentArguments()`.
Expand Down
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@
- Restored the “Preview” and “Edit Image” buttons on asset image previews. ([#14333](https://github.com/craftcms/cms/discussions/14333))
- Improved the accessibility of the global sidebar. ([#14335](https://github.com/craftcms/cms/pull/14335))
- Improved the contrast of the Dev Mode caution tape indicator. ([#14336](https://github.com/craftcms/cms/pull/14336))
- GraphQL schemas can now include queries and mutations for nested entries (e.g. within Matrix or CKEditor fields) directly. ([#14366](https://github.com/craftcms/cms/pull/14366))
- Added the `fieldId`, `fieldHandle`, `ownerId`, and `sortOrder` entry GraphQL fields. ([#14366](https://github.com/craftcms/cms/pull/14366))
- Nested addresses are now cached by their field ID, and address queries now register cache tags based on their `field` and `fieldId` params.
- Nested entries are now cached by their field ID, and entry queries now register cache tags based on their `field` and `fieldId` params.
- Added `craft\services\Fields::EVENT_REGISTER_NESTED_ENTRY_FIELD_TYPES`.
- Added `craft\services\Fields::getNestedEntryFieldTypes()`.
- Added `craft\services\Relations::deleteLeftoverRelations()`. ([#13956](https://github.com/craftcms/cms/issues/13956))
- `craft\behaviors\SessionBehavior::setSuccess()` and `getSuccess()` use the `success` flash key now, rather than `notice`. ([#14345](https://github.com/craftcms/cms/pull/14345))
- 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))
- Fixed a bug where read/write splitting was always getting disabled for GraphQL POST requests. ([#14324](https://github.com/craftcms/cms/issues/14324))
- 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))
- Fixed an error that occurred when adding a new block to a Matrix field with an overridden handle. ([#14350](https://github.com/craftcms/cms/issues/14350))
- Fixed a bug where opening a disclosure menu wasn’t hiding others. ([#14354](https://github.com/craftcms/cms/issues/14354))
- 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 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))
- Fixed a bug where element slideouts would open when long-pressing on an element’s “Remove” button within an element select input.

## 5.0.0-beta.1 - 2024-02-08

Expand Down
5 changes: 5 additions & 0 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -5693,6 +5693,11 @@ public function afterPropagate(bool $isNew): void
$field->afterElementPropagate($this, $isNew);
}

// Delete relations that don’t belong to a relational field on the element's field layout
if (!ElementHelper::isDraftOrRevision($this)) {
Craft::$app->getRelations()->deleteLeftoverRelations($this);
}

// Trigger an 'afterPropagate' event
if ($this->hasEventHandlers(self::EVENT_AFTER_PROPAGATE)) {
$this->trigger(self::EVENT_AFTER_PROPAGATE, new ModelEvent([
Expand Down
14 changes: 14 additions & 0 deletions src/elements/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,20 @@ public function defineRules(): array
return $rules;
}

/**
* @inheritdoc
*/
protected function cacheTags(): array
{
$tags = [];

if (isset($this->fieldId)) {
$tags[] = "field:$this->fieldId";
}

return $tags;
}

/**
* @inheritdoc
* @throws InvalidConfigException
Expand Down
1 change: 1 addition & 0 deletions src/elements/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -2740,6 +2740,7 @@ protected function metaFieldsHtml(bool $static): string
return implode("\n", [
Cp::textFieldHtml([
'label' => Craft::t('app', 'Filename'),
'attribute' => 'newLocation',
'id' => 'new-filename',
'name' => 'newFilename',
'value' => $this->_filename,
Expand Down
2 changes: 2 additions & 0 deletions src/elements/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,8 @@ protected function cacheTags(): array

if (isset($this->sectionId)) {
$tags[] = "section:$this->sectionId";
} elseif (isset($this->fieldId)) {
$tags[] = "field:$this->fieldId";
}

return $tags;
Expand Down
7 changes: 7 additions & 0 deletions src/elements/db/AddressQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -1391,11 +1391,18 @@ protected function cacheTags(): array
{
$tags = [];

if ($this->fieldId) {
foreach ($this->fieldId as $fieldId) {
$tags[] = "field:$fieldId";
}
}

if ($this->primaryOwnerId) {
foreach ($this->primaryOwnerId as $ownerId) {
$tags[] = "element::$ownerId";
}
}

if ($this->ownerId) {
foreach ($this->ownerId as $ownerId) {
$tags[] = "element::$ownerId";
Expand Down
6 changes: 6 additions & 0 deletions src/elements/db/EntryQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -1641,12 +1641,18 @@ protected function cacheTags(): array
foreach ($this->sectionId as $sectionId) {
$tags[] = "section:$sectionId";
}
} elseif ($this->fieldId) {
foreach ($this->fieldId as $fieldId) {
$tags[] = "field:$fieldId";
}
}

if ($this->primaryOwnerId) {
foreach ($this->primaryOwnerId as $ownerId) {
$tags[] = "element::$ownerId";
}
}

if ($this->ownerId) {
foreach ($this->ownerId as $ownerId) {
$tags[] = "element::$ownerId";
Expand Down
20 changes: 20 additions & 0 deletions src/gql/arguments/elements/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
38 changes: 38 additions & 0 deletions src/gql/arguments/mutations/NestedEntry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\gql\arguments\mutations;

use GraphQL\Type\Definition\Type;

/**
* Class NestedEntry
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @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.',
],
]);
}
}
25 changes: 23 additions & 2 deletions src/gql/interfaces/elements/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
89 changes: 88 additions & 1 deletion src/gql/mutations/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -45,7 +47,7 @@ public static function getMutations(): array
$canSave = Gql::canSchema($scope, 'save');

if ($canCreate || $canSave) {
// Create a mutation for each editable section that includes the entry type
// Create a mutation for each entry type
foreach ($section->getEntryTypes() as $entryType) {
foreach (static::createSaveMutations($section, $entryType, $canSave) as $mutation) {
$mutationList[$mutation['name']] = $mutation;
Expand All @@ -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);

Expand Down Expand Up @@ -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;
}
}
Loading

0 comments on commit 30537dd

Please sign in to comment.