Skip to content

Commit 8758c59

Browse files
authored
Merge pull request #17779 from craftcms/brandon/cms-1551-content-section
Customizable entry index pages
2 parents c9a3728 + 6dd3b7f commit 8758c59

34 files changed

+1605
-412
lines changed

CHANGELOG-WIP.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- The Image Editor’s focal point tool is now keyboard accessible. ([#17880](https://github.com/craftcms/cms/pull/17880))
1212

1313
### Administration
14+
- It’s now possible to divide entry sources into multiple index pages, via the Customize Sources modal. ([#17779](https://github.com/craftcms/cms/pull/17779))
1415
- Added the “View user” GraphQL schema option for Craft Solo. ([#17863](https://github.com/craftcms/cms/pull/17863))
1516
- Users’ User Groups settings now show a component select input, and support inline group editing/creation on environments that allow administrative changes.
1617
- Relational fields now have an “Inline list” view mode. ([#17744](https://github.com/craftcms/cms/pull/17744))
@@ -22,6 +23,7 @@
2223
- “Field” and “Section” condition rules now show field/section handles for users with the “Show field handles in edit forms” preference enabled. ([#17909](https://github.com/craftcms/cms/pull/17909))
2324
- “Remove” actions on the Plugins index page now show a confirmation dialog. ([#17922](https://github.com/craftcms/cms/pull/17922))
2425
- `entrify` commands no longer require a category group/tag group/global set handle to be passed.
26+
- `entrify` commands now automatically assign newly-created channel/structure sections to “Categories” or “Tags” pages. ([#17779](https://github.com/craftcms/cms/pull/17779))
2527
- Added the `useIdnaNontransitionalToUnicode` config setting. ([#17946](https://github.com/craftcms/cms/pull/17946))
2628

2729
### Development
@@ -38,6 +40,7 @@
3840
- Added `Craft.BaseElementIndex::asyncSelectSource()`.
3941
- Added `Craft.BaseElementIndex::asyncSelectSourceByKey()`.
4042
- Added `Craft.BaseElementIndex::ensureSourceAttributeInfo()`.
43+
- Added `craft\base\ElementIndex::multiPageSources()`. ([#17779](https://github.com/craftcms/cms/pull/17779))
4144
- Added `craft\base\ElementTrait::$hasProvisionalChanges`. ([#17915](https://github.com/craftcms/cms/pull/17915))
4245
- Added `craft\elements\conditions\HintableConditionRuleTrait`. ([#17909](https://github.com/craftcms/cms/pull/17909))
4346
- Added `craft\events\RegisterElementCardAttributesEvent::$fieldLayout`. ([#17920](https://github.com/craftcms/cms/pull/17920))
@@ -48,12 +51,19 @@
4851
- Added `craft\fields\BaseRelationField::VIEW_MODE_THUMBS`.
4952
- Added `craft\fields\Matrix::VIEW_MODE_CARDS_GRID`.
5053
- Added `craft\helpers\ElementHelper::loadProvisionalChanges()`. ([#17915](https://github.com/craftcms/cms/pull/17915))
54+
- Added `craft\services\ElementSources::getFirstPage()`. ([#17779](https://github.com/craftcms/cms/pull/17779))
55+
- Added `craft\services\ElementSources::getPageSettings()`. ([#17779](https://github.com/craftcms/cms/pull/17779))
56+
- Added `craft\services\ElementSources::getPages()`. ([#17779](https://github.com/craftcms/cms/pull/17779))
57+
- Added `craft\services\ElementSources::pageExists()`. ([#17779](https://github.com/craftcms/cms/pull/17779))
58+
- Added `craft\services\ElementSources::pageNameId()`. ([#17779](https://github.com/craftcms/cms/pull/17779))
5159
- Added `craft\web\GqlResponseFormatter`.
5260
- Added `craft\web\Response::FORMAT_GQL`.
5361
- Added `craft\web\twig\nodes\BaseNode`.
5462
- `craft\base\ElementInterface::cardAttributes()` now has a `$fieldLayout` argument. ([#17920](https://github.com/craftcms/cms/pull/17920))
5563
- `craft\helpers\FileHelper::writeToFile()` now throws an exception if the file path isn’t writable, or there isn’t sufficient free space on the disk. ([#17762](https://github.com/craftcms/cms/pull/17762))
5664
- `craft\helpers\UrlHelper` now encodes square brackets in generated URLs. ([#17840](https://github.com/craftcms/cms/pull/17840))
65+
- `craft\services\ElementSources::getSources()` now has a `$page` argument. ([#17779](https://github.com/craftcms/cms/pull/17779))
66+
- `craft\services\ElementSources::sourceExists()` now has a `$page` argument. ([#17779](https://github.com/craftcms/cms/pull/17779))
5767
- `craft\web\Request::accepts()` now accepts wildcard characters (`*`) in the `$contentType` argument, to check for a range of MIME types (e.g. `application/*+json`).
5868
- `craft\web\Request::getAcceptsJson()` now returns `true` for requests with `Content-Type` headers that match `application/*+json`, in addition to `application/json`.
5969
- Deprecated `craft\fields\BaseRelationField::$showCardsInGrid`.

src/base/Element.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,14 @@ public static function createCondition(): ElementConditionInterface
975975
return Craft::createObject(ElementCondition::class, [static::class]);
976976
}
977977

978+
/**
979+
* @inheritdoc
980+
*/
981+
public static function multiPageSources(): bool
982+
{
983+
return false;
984+
}
985+
978986
/**
979987
* @inheritdoc
980988
*/

src/base/ElementInterface.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,21 @@ public static function findAll(mixed $criteria = null): array;
261261
*/
262262
public static function createCondition(): ElementConditionInterface;
263263

264+
/**
265+
* Returns whether the element type’s sources can be split into multiple pages.
266+
*
267+
* @return bool
268+
* @since 5.9.0
269+
*/
270+
public static function multiPageSources(): bool;
271+
264272
/**
265273
* Returns the source definitions that elements of this type may belong to.
266274
*
267275
* This defines what will show up in the source list on element indexes and element selector modals.
268276
*
269277
* Each item in the array should be set to an array that has the following keys:
278+
* - **`page`** – The source’s page label. (Optional)
270279
* - **`key`** – The source’s key. This is the string that will be passed into the $source argument of [[actions()]],
271280
* [[indexHtml()]], and [[defaultTableAttributes()]].
272281
* - **`label`** – The human-facing label of the source.

src/config/cproutes/common.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
'entries/<section:{handle}>/new' => 'entries/create',
1818
'entries/<section:{handle}>/<elementId:\d+><slug:(?:-[^\/]*)?>' => 'elements/edit',
1919
'entries/<section:{handle}>/<elementId:\d+><slug:(?:-[^\/]*)?>/revisions' => 'elements/revisions',
20+
21+
'content' => 'entries',
22+
'content/<page:{slug}>' => ['template' => 'entries'],
23+
'content/<page:{slug}>/<sectionHandle:{handle}>' => ['template' => 'entries'],
24+
'content/<page:{slug}>/<section:{handle}>/new' => 'entries/create',
25+
'content/<page:{slug}>/<section:{handle}>/<elementId:\d+><slug:(?:-[^\/]*)?>' => 'elements/edit',
26+
'content/<page:{slug}>/<section:{handle}>/<elementId:\d+><slug:(?:-[^\/]*)?>/revisions' => 'elements/revisions',
27+
2028
'globals' => 'globals',
2129
'globals/<globalSetHandle:{handle}>' => 'globals/edit-content',
2230
'graphiql' => 'graphql/graphiql',

src/console/controllers/EntrifyController.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ public function actionCategories(?string $categoryGroup = null): int
145145
$this->run('sections/create', [
146146
'fromCategoryGroup' => $categoryGroup->handle,
147147
]);
148+
149+
// Add it to a “Categories” page
150+
$this->_addSectionToPage('Categories', 'sitemap');
151+
148152
$projectConfigChanged = true;
149153
$sectionCreated = true;
150154
}
@@ -371,6 +375,10 @@ public function actionTags(?string $tagGroup = null): int
371375
$this->run('sections/create', [
372376
'fromTagGroup' => $tagGroup->handle,
373377
]);
378+
379+
// Add it to a “Tags” page
380+
$this->_addSectionToPage('Tags', 'tags');
381+
374382
$projectConfigChanged = true;
375383
}
376384

@@ -808,4 +816,26 @@ private function _deployTip(string $action, string $handle): void
808816
```
809817
MD);
810818
}
819+
820+
private function _addSectionToPage(string $name, string $icon): void
821+
{
822+
$projectConfig = Craft::$app->getProjectConfig();
823+
824+
$sourceConfigPath = sprintf('%s.%s', ProjectConfig::PATH_ELEMENT_SOURCES, Entry::class);
825+
$sourceConfigs = Collection::make($projectConfig->get($sourceConfigPath))
826+
->map(fn(array $config) => $config + ['page' => 'Entries'])
827+
->all();
828+
$sourceConfigs[] = [
829+
'key' => sprintf('section:%s', $this->_section()->uid),
830+
'page' => $name,
831+
'type' => 'native',
832+
];
833+
$projectConfig->set($sourceConfigPath, $sourceConfigs);
834+
835+
$pageSettings = Craft::$app->getElementSources()->getPageSettings(Entry::class);
836+
$pageSettings[$name] = [
837+
'icon' => $icon,
838+
];
839+
$projectConfig->set(sprintf('%s.%s', ProjectConfig::PATH_ELEMENT_SOURCE_PAGES, Entry::class), $pageSettings);
840+
}
811841
}

src/controllers/ElementIndexSettingsController.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,17 @@ public function actionGetCustomizeSourcesModalData(): Response
6868
// Get the source info
6969
$sourcesService = Craft::$app->getElementSources();
7070
$sources = $sourcesService->getSources($elementType, ElementSources::CONTEXT_INDEX, true);
71+
$multiPage = $elementType::multiPageSources();
7172

7273
foreach ($sources as &$source) {
74+
if ($multiPage) {
75+
// ensure we're using the EN translation here
76+
$language = Craft::$app->language;
77+
Craft::$app->language = Craft::$app->sourceLanguage;
78+
$source['page'] ??= $elementType::pluralDisplayName();
79+
Craft::$app->language = $language;
80+
}
81+
7382
if ($source['type'] === ElementSources::TYPE_HEADING) {
7483
continue;
7584
}
@@ -215,8 +224,12 @@ public function actionGetCustomizeSourcesModalData(): Response
215224
])
216225
->all();
217226

227+
$pageSettings = $sourcesService->getPageSettings($elementType);
228+
218229
return $this->asJson([
230+
'multiPage' => $multiPage,
219231
'sources' => $sources,
232+
'pageSettings' => $pageSettings,
220233
'viewModes' => $viewModes,
221234
'baseSortOptions' => $baseSortOptions,
222235
'defaultSortOptions' => $defaultSortOptions,
@@ -239,6 +252,7 @@ public function actionGetCustomizeSourcesModalData(): Response
239252
public function actionSaveCustomizeSourcesModalSettings(): Response
240253
{
241254
$elementType = $this->elementType();
255+
$multiPage = $elementType::multiPageSources();
242256

243257
// Get the old source configs
244258
$projectConfig = Craft::$app->getProjectConfig();
@@ -252,6 +266,11 @@ public function actionSaveCustomizeSourcesModalSettings(): Response
252266
$newSourceConfigs = [];
253267
$disabledSourceKeys = [];
254268

269+
if ($multiPage) {
270+
$sourcePages = $this->request->getBodyParam('sourcePages', []);
271+
$pageSettings = $this->request->getBodyParam('pageSettings', []);
272+
}
273+
255274
// Normalize to the way it's stored in the DB
256275
foreach ($sourceOrder as $source) {
257276
if (isset($source['key'])) {
@@ -267,6 +286,10 @@ public function actionSaveCustomizeSourcesModalSettings(): Response
267286
'key' => $source['key'],
268287
];
269288

289+
if (isset($sourcePages[$source['key']])) {
290+
$sourceConfig['page'] = $sourcePages[$source['key']];
291+
}
292+
270293
// Were new settings posted?
271294
if (isset($sourceSettings[$source['key']])) {
272295
$postedSettings = $sourceSettings[$source['key']];
@@ -318,7 +341,14 @@ public function actionSaveCustomizeSourcesModalSettings(): Response
318341
}
319342
}
320343

321-
$projectConfig->set(ProjectConfig::PATH_ELEMENT_SOURCES . ".$elementType", $newSourceConfigs);
344+
$projectConfig->set(sprintf('%s.%s', ProjectConfig::PATH_ELEMENT_SOURCES, $elementType), $newSourceConfigs);
345+
346+
if ($multiPage) {
347+
$projectConfig->set(
348+
sprintf('%s.%s', ProjectConfig::PATH_ELEMENT_SOURCE_PAGES, $elementType),
349+
array_map('array_filter', $pageSettings),
350+
);
351+
}
322352

323353
Craft::$app->getSession()->setSuccess(Craft::t('app', 'Source settings saved'));
324354

src/controllers/EntriesController.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@
4343
*/
4444
class EntriesController extends BaseEntriesController
4545
{
46+
/**
47+
* @since 5.9.0
48+
*/
49+
public function actionIndex(): Response
50+
{
51+
$firstPage = Craft::$app->getElementSources()->getFirstPage(Entry::class);
52+
$slug = $firstPage ? StringHelper::toKebabCase($firstPage) : 'entries';
53+
return $this->redirect("content/$slug");
54+
}
55+
4656
/**
4757
* Creates a new unpublished draft and redirects to its edit page.
4858
*

src/elements/Entry.php

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
use craft\helpers\Db;
5656
use craft\helpers\ElementHelper;
5757
use craft\helpers\Html;
58+
use craft\helpers\StringHelper;
5859
use craft\helpers\UrlHelper;
5960
use craft\models\EntryType;
6061
use craft\models\FieldLayout;
@@ -236,6 +237,14 @@ public static function createCondition(): ElementConditionInterface
236237
return Craft::createObject(EntryCondition::class, [static::class]);
237238
}
238239

240+
/**
241+
* @inheritdoc
242+
*/
243+
public static function multiPageSources(): bool
244+
{
245+
return true;
246+
}
247+
239248
/**
240249
* @inheritdoc
241250
*/
@@ -931,6 +940,11 @@ protected static function prepElementQueryForTableAttribute(ElementQueryInterfac
931940
*/
932941
private ?EntryType $_type = null;
933942

943+
/**
944+
* @see page()
945+
*/
946+
private string|false $page;
947+
934948
/**
935949
* @inheritdoc
936950
* @since 3.5.0
@@ -1273,10 +1287,12 @@ protected function crumbs(): array
12731287
return [];
12741288
}
12751289

1290+
$page = $this->page();
1291+
12761292
$crumbs = [
12771293
[
1278-
'label' => Craft::t('app', 'Entries'),
1279-
'url' => 'entries',
1294+
'label' => $page && $page !== 'Entries' ? Craft::t('site', $page) : Craft::t('app', 'Entries'),
1295+
'url' => sprintf('content/%s', $page ? StringHelper::toKebabCase($page) : 'entries'),
12801296
],
12811297
];
12821298

@@ -2132,7 +2148,13 @@ protected function cpEditUrl(): ?string
21322148
return ElementHelper::elementEditorUrl($this, false);
21332149
}
21342150

2135-
$path = sprintf('entries/%s/%s', $section->handle, $this->getCanonicalId());
2151+
$page = $this->page();
2152+
$path = sprintf(
2153+
'content/%s/%s/%s',
2154+
$page ? StringHelper::toKebabCase($page) : 'entries',
2155+
$section->handle,
2156+
$this->getCanonicalId(),
2157+
);
21362158

21372159
// Ignore homepage/temp slugs
21382160
if ($this->slug && !str_starts_with($this->slug, '__')) {
@@ -3210,4 +3232,20 @@ protected function partialTemplatePathCandidates(): array
32103232

32113233
return $templates;
32123234
}
3235+
3236+
private function page(): ?string
3237+
{
3238+
if (!isset($this->page)) {
3239+
$section = $this->getSection();
3240+
if ($section) {
3241+
$sourceKey = $section->type === Section::TYPE_SINGLE ? 'singles' : "section:$section->uid";
3242+
$source = ElementHelper::findSource(Entry::class, $sourceKey);
3243+
$this->page = $source['page'] ?? false;
3244+
} else {
3245+
$this->page = false;
3246+
}
3247+
}
3248+
3249+
return $this->page ?: null;
3250+
}
32133251
}

0 commit comments

Comments
 (0)