Skip to content

Commit

Permalink
Merge branch '5.0' into feature/cms-1262-addressquery-querying-by-pri…
Browse files Browse the repository at this point in the history
…maryownerid
  • Loading branch information
brandonkelly committed Feb 15, 2024
2 parents 2fb53a2 + 460889a commit 60bc069
Show file tree
Hide file tree
Showing 28 changed files with 413 additions and 150 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
- Entry types are now managed independently of sections.
- Entry types are no longer required to have a Title Format, if the Title field isn’t shown.
- Entry types now have a “Show the Slug field” setting. ([#13799](https://github.com/craftcms/cms/discussions/13799))
- Entry type and field edit pages now list their usages. ([#14397](https://github.com/craftcms/cms/pull/14397))
- Sites’ Language settings can now be set to environment variables. ([#14235](https://github.com/craftcms/cms/pull/14235), [#14135](https://github.com/craftcms/cms/discussions/14135))
- Matrix fields now manage nested entries, rather than Matrix blocks. During the upgrade, existing Matrix block types will be converted to entry types; their nested fields will be made global; and Matrix blocks will be converted to entries.
- Matrix fields now have “Entry URI Format” and “Template” settings for each site.
Expand Down Expand Up @@ -144,6 +145,7 @@
- Added `craft\base\ApplicationTrait::getAuth()`.
- Added `craft\base\Chippable`. ([#14169](https://github.com/craftcms/cms/pull/14169))
- Added `craft\base\Colorable`. ([#14187](https://github.com/craftcms/cms/pull/14187))
- Added `craft\base\CpEditable`.
- Added `craft\base\Element::EVENT_DEFINE_ACTION_MENU_ITEMS`.
- Added `craft\base\Element::EVENT_DEFINE_INLINE_ATTRIBUTE_INPUT_HTML`.
- Added `craft\base\Element::crumbs()`.
Expand Down Expand Up @@ -319,6 +321,7 @@
- Added `craft\log\Dispatcher::getDefaultTarget()`. ([#14283](https://github.com/craftcms/cms/pull/14283))
- Added `craft\migrations\BaseContentRefactorMigration`.
- Added `craft\models\EntryType::$color`.
- Added `craft\models\EntryType::findUsages()`.
- Added `craft\models\FieldLayout::getCardBodyFields()`.
- Added `craft\models\FieldLayout::getElementByUid()`.
- Added `craft\models\FieldLayout::getFieldById()`.
Expand Down Expand Up @@ -362,6 +365,7 @@
- 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::findFieldUsages()`.
- Added `craft\services\Fields::getAllLayouts()`.
- Added `craft\services\Fields::getNestedEntryFieldTypes()`.
- Added `craft\services\Gql::defineContentArgumentsForFieldLayouts()`.
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

## Unreleased

- Entry type and field edit pages now list their usages. ([#14397](https://github.com/craftcms/cms/pull/14397))
- 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.
- Improved the global sidebar for text zoom. ([#14351](https://github.com/craftcms/cms/pull/14351))
- Added `craft\base\CpEditable`.
- Added `craft\models\EntryType::findUsages()`.
- Added `craft\services\Fields::EVENT_REGISTER_NESTED_ENTRY_FIELD_TYPES`.
- Added `craft\services\Fields::findFieldUsages()`.
- Added `craft\services\Fields::getNestedEntryFieldTypes()`.
- Added `craft\services\ProjectConfig::EVENT_AFTER_WRITE_YAML_FILES`. ([#14365](https://github.com/craftcms/cms/discussions/14365))
- Added `craft\services\Relations::deleteLeftoverRelations()`. ([#13956](https://github.com/craftcms/cms/issues/13956))
Expand All @@ -26,6 +31,7 @@
- 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 nested entries could become orphaned when pressing “Save as a new entry”. ([#14378](https://github.com/craftcms/cms/issues/14378))
- 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))
Expand Down
25 changes: 25 additions & 0 deletions src/base/CpEditable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\base;

/**
* CpEditable defines the common interface to be implemented by components that
* have a dedicated edit page in the control panel.
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 5.0.0
*/
interface CpEditable
{
/**
* Returns the URL to the component’s edit page in the control panel.
*
* @return string|null
*/
public function getCpEditUrl(): ?string;
}
15 changes: 7 additions & 8 deletions src/base/ElementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 3.0.0
*/
interface ElementInterface extends ComponentInterface, Chippable, Thumbable, Statusable, Actionable
interface ElementInterface extends
ComponentInterface,
Chippable,
CpEditable,
Thumbable,
Statusable,
Actionable
{
/**
* Returns the lowercase version of [[displayName()]].
Expand Down Expand Up @@ -907,13 +913,6 @@ public function hasRevisions(): bool;
*/
public function prepareEditScreen(Response $response, string $containerId): void;

/**
* Returns the element’s edit URL in the control panel.
*
* @return string|null
*/
public function getCpEditUrl(): ?string;

/**
* Returns the URL that users should be redirected to after editing the element.
*
Expand Down
47 changes: 45 additions & 2 deletions src/controllers/EntryTypesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
namespace craft\controllers;

use Craft;
use craft\base\ElementContainerFieldInterface;
use craft\elements\Entry;
use craft\enums\Color;
use craft\helpers\Cp;
use craft\helpers\Html;
use craft\helpers\UrlHelper;
use craft\models\EntryType;
use craft\models\Section;
use craft\web\Controller;
Expand Down Expand Up @@ -67,7 +71,7 @@ public function actionEdit(?int $entryTypeId = null, ?EntryType $entryType = nul
}

return $this->asCpScreen()
->editUrl($entryType->id ? "settings/entry-types/$entryType->id" : null)
->editUrl($entryType->getCpEditUrl())
->title($title)
->addCrumb(Craft::t('app', 'Settings'), 'settings')
->addCrumb(Craft::t('app', 'Entry Types'), 'settings/entry-types')
Expand All @@ -83,7 +87,46 @@ public function actionEdit(?int $entryTypeId = null, ?EntryType $entryType = nul
'entryType' => $entryType,
'typeName' => Entry::displayName(),
'lowerTypeName' => Entry::lowerDisplayName(),
]);
])
->metaSidebarHtml($entryType->id ? Cp::metadataHtml([
Craft::t('app', 'ID') => $entryType->id,
Craft::t('app', 'Used by') => function() use ($entryType) {
$usages = $entryType->findUsages();
if (empty($usages)) {
return Html::tag('i', Craft::t('app', 'No usages'));
}

$labels = [];
$items = array_map(function(Section|ElementContainerFieldInterface $usage) use (&$labels) {
if ($usage instanceof Section) {
$label = Craft::t('site', $usage->name);
$url = $usage->getCpEditUrl();
$icon = 'newspaper';
} else {
$label = Craft::t('site', $usage->name);
$url = UrlHelper::cpUrl("settings/fields/edit/$usage->id");
$icon = $usage::icon();
}
$labels[] = $label;
$labelHtml = Html::beginTag('span', [
'class' => ['flex', 'flex-nowrap', 'gap-s'],
]) .
Html::tag('div', Cp::iconSvg($icon), [
'class' => ['cp-icon', 'small'],
]) .
Html::tag('span', Html::encode($label)) .
Html::endTag('span');
return Html::a($labelHtml, $url);
}, $entryType->findUsages());

// sort by label
array_multisort($labels, SORT_ASC, $items);

return Html::ul($items, [
'encode' => false,
]);
},
]) : null);
}

/**
Expand Down
108 changes: 100 additions & 8 deletions src/controllers/FieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
namespace craft\controllers;

use Craft;
use craft\base\Chippable;
use craft\base\Colorable;
use craft\base\CpEditable;
use craft\base\ElementInterface;
use craft\base\Field;
use craft\base\FieldInterface;
use craft\base\FieldLayoutProviderInterface;
use craft\base\Iconic;
use craft\fieldlayoutelements\CustomField;
use craft\fields\MissingField;
use craft\fields\PlainText;
Expand All @@ -18,6 +24,7 @@
use craft\helpers\Html;
use craft\helpers\StringHelper;
use craft\helpers\UrlHelper;
use craft\models\FieldLayout;
use craft\models\FieldLayoutTab;
use craft\web\assets\fieldsettings\FieldSettingsAsset;
use craft\web\Controller;
Expand Down Expand Up @@ -198,14 +205,99 @@ public function actionEditField(?int $fieldId = null, ?FieldInterface $field = n
});

if ($field->id) {
$response->addAltAction(Craft::t('app', 'Delete'), [
'action' => 'fields/delete-field',
'redirect' => 'settings/fields',
'destructive' => true,
'confirm' => Craft::t('app', 'Are you sure you want to delete “{name}”?', [
'name' => $field->name,
]),
]);
$response
->addAltAction(Craft::t('app', 'Delete'), [
'action' => 'fields/delete-field',
'redirect' => 'settings/fields',
'destructive' => true,
'confirm' => Craft::t('app', 'Are you sure you want to delete “{name}”?', [
'name' => $field->name,
]),
])
->metaSidebarHtml(Cp::metadataHtml([
Craft::t('app', 'ID') => $field->id,
Craft::t('app', 'Used by') => function() use ($fieldsService, $field) {
$layouts = $fieldsService->findFieldUsages($field);
if (empty($layouts)) {
return Html::tag('i', Craft::t('app', 'No usages'));
}

/** @var FieldLayout[][] $layoutsByType */
$layoutsByType = ArrayHelper::index($layouts,
fn(FieldLayout $layout) => $layout->uid,
[fn(FieldLayout $layout) => $layout->type ?? '__UNKNOWN__'],
);
/** @var FieldLayout[] $unknownLayouts */
$unknownLayouts = ArrayHelper::remove($layoutsByType, '__UNKNOWN__');
/** @var FieldLayout[] $layoutsWithProviders */
$layoutsWithProviders = [];

// re-fetch as many of these as we can from the element types,
// so they have a chance to supply the layout providers
foreach ($layoutsByType as $type => &$typeLayouts) {
/** @var string|ElementInterface $type */
/** @phpstan-ignore-next-line */
foreach ($type::fieldLayouts(null) as $layout) {
if (isset($typeLayouts[$layout->uid]) && $layout->provider instanceof Chippable) {
$layoutsWithProviders[] = $layout;
unset($typeLayouts[$layout->uid]);
}
}
}
unset($typeLayouts);

$labels = [];
$items = array_map(function(FieldLayout $layout) use (&$labels) {
/** @var FieldLayoutProviderInterface&Chippable $provider */
$provider = $layout->provider;
$label = $labels[] = $provider->getUiLabel();
$url = $provider instanceof CpEditable ? $provider->getCpEditUrl() : null;
$icon = $provider instanceof Iconic ? $provider->getIcon() : null;

$labelHtml = Html::beginTag('span', [
'class' => ['flex', 'flex-nowrap', 'gap-s'],
]);
if ($icon) {
$labelHtml .= Html::tag('div', Cp::iconSvg($icon), [
'class' => array_filter([
'cp-icon',
'small',
$provider instanceof Colorable ? $provider->getColor()?->value : null,
]),
]);
}
$labelHtml .= Html::tag('span', Html::encode($label)) .
Html::endTag('span');

return $url ? Html::a($labelHtml, $url) : $labelHtml;
}, $layoutsWithProviders);

// sort by label
array_multisort($labels, SORT_ASC, $items);

foreach ($layoutsByType as $type => $typeLayouts) {
// any remaining layouts for this type?
if (!empty($typeLayouts)) {
/** @var string|ElementInterface $type */
$items[] = Craft::t('app', '{total, number} {type} {total, plural, =1{field layout} other{field layouts}}', [
'total' => count($typeLayouts),
'type' => $type::lowerDisplayName(),
]);
}
}

if (!empty($unknownLayouts)) {
$items[] = Craft::t('app', '{total, number} {type} {total, plural, =1{field layout} other{field layouts}}', [
'total' => count($unknownLayouts),
'type' => Craft::t('app', 'unknown'),
]);
}

return Html::ul($items, [
'encode' => false,
]);
},
]));
}

return $response;
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/VolumesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function actionEditVolume(?int $volumeId = null, ?Volume $volume = null):
'shortcut' => true,
'retainScroll' => true,
])
->editUrl($volume->id ? "settings/assets/volumes/$volume->id" : null)
->editUrl($volume->getCpEditUrl())
->contentTemplate('settings/assets/volumes/_edit.twig', [
'volumeId' => $volumeId,
'volume' => $volume,
Expand Down
1 change: 1 addition & 0 deletions src/elements/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -2631,6 +2631,7 @@ public function getPreviewHtml(): string
$imageButtonHtml .= Html::button(Craft::t('app', 'Preview'), [
'id' => 'preview-btn',
'class' => ['btn', 'preview-btn'],
'aria-label' => Craft::t('app', 'Preview'),
]);

$previewBtnId = $view->namespaceInputId('preview-btn');
Expand Down
11 changes: 6 additions & 5 deletions src/elements/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -2320,14 +2320,15 @@ public function afterSave(bool $isNew): void
}
}

// ownerId will be null when creating a revision
if (isset($this->fieldId, $this->ownerId) && $this->saveOwnership) {
// getPrimaryOwnerId will return null when creating a revision
$primaryOwnerId = $this->getPrimaryOwnerId();
if (isset($this->fieldId, $primaryOwnerId) && $this->saveOwnership) {
if (!isset($this->sortOrder)) {
$max = (new Query())
->from(['eo' => Table::ELEMENTS_OWNERS])
->innerJoin(['e' => Table::ENTRIES], '[[e.id]] = [[eo.elementId]]')
->where([
'eo.ownerId' => $this->ownerId,
'eo.ownerId' => $primaryOwnerId,
'e.fieldId' => $this->fieldId,
])
->max('[[eo.sortOrder]]');
Expand All @@ -2336,15 +2337,15 @@ public function afterSave(bool $isNew): void
if ($isNew) {
Db::insert(Table::ELEMENTS_OWNERS, [
'elementId' => $this->id,
'ownerId' => $this->ownerId,
'ownerId' => $primaryOwnerId,
'sortOrder' => $this->sortOrder,
]);
} else {
Db::update(Table::ELEMENTS_OWNERS, [
'sortOrder' => $this->sortOrder,
], [
'elementId' => $this->id,
'ownerId' => $this->ownerId,
'ownerId' => $primaryOwnerId,
]);
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/elements/GlobalSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ public static function isLocalized(): bool
return true;
}

/**
* @inheritdoc
*/
protected static function defineFieldLayouts(?string $source): array
{
// fetch them through the global set instances so $provider gets set
return array_map(fn(self $globalSet) => $globalSet->getFieldLayout(), self::findAll());
}

/**
* @return string|null
*/
Expand Down
Loading

0 comments on commit 60bc069

Please sign in to comment.