Skip to content

Commit 305743d

Browse files
authored
Merge pull request #18680 from craftcms/feature/explicit-redirects
Explicitly set `returnUrl` params on element edit pages
2 parents 1e32f0b + 211065c commit 305743d

15 files changed

Lines changed: 84 additions & 8 deletions

File tree

CHANGELOG-WIP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
### Content Management
44
- Collapsed Matrix blocks now show their entries’ UI labels as preview text, whenever possible. ([#18484](https://github.com/craftcms/cms/discussions/18484))
55
- Element-level actions within nested element management fields (Matrix, Addresses, etc.) now consistently affect all selected elements, when performed on a selected element. ([#18561](https://github.com/craftcms/cms/pull/18561))
6-
- Elements within Matrix and Addresses fields now have “Paste above” actions when a compatible element is copied. ([#17406](https://github.com/craftcms/cms/discussions/17406))
6+
- Elements within Matrix and Addresses fields now have “Paste above” actions when a compatible element is copied. ([#17406](https://github.com/craftcms/cms/discussions/17406))
7+
- Elements now keep track of the index page’s URL their edit page was linked to from, and explicitly redirect back to that page after save, rather than always redirecting to the referrer. ([#18680](https://github.com/craftcms/cms/pull/18680))
78
- Addresses fields now have a “Copy all addresses” field-level action. ([#18561](https://github.com/craftcms/cms/pull/18561))
89
- Matrix fields’ “Expand”, “Collapse”, and “Copy” field-level actions now always affect all nested entries, regardless of whether any entries are selected. ([#18561](https://github.com/craftcms/cms/pull/18561))
910
- Matrix fields no longer have “Duplicate” and “Delete” field-level actions. ([#18561](https://github.com/craftcms/cms/pull/18561))

src/base/Element.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
use craft\helpers\Db;
8181
use craft\helpers\ElementHelper;
8282
use craft\helpers\Html;
83+
use craft\helpers\Json;
8384
use craft\helpers\StringHelper;
8485
use craft\helpers\Template;
8586
use craft\helpers\UrlHelper;
@@ -1285,6 +1286,7 @@ public static function indexHtml(
12851286
'nestedInputNamespace' => $viewState['nestedInputNamespace'] ?? null,
12861287
'tableName' => static::pluralDisplayName(),
12871288
'elementQuery' => self::elementQueryWithAllDescendants($elementQuery),
1289+
'returnUrl' => $viewState['returnUrl'] ?? null,
12881290
];
12891291

12901292
$db = Craft::$app->getDb();
@@ -3940,12 +3942,20 @@ public function getAltActions(): array
39403942
$elementsService = Craft::$app->getElements();
39413943
$canSaveCanonical = $elementsService->canSaveCanonical($this);
39423944

3945+
$returnUrl = Craft::$app->getRequest()->getQueryParam('returnUrl');
3946+
$redirectParams = array_filter([
3947+
'returnUrl' => $returnUrl,
3948+
]);
3949+
39433950
$altActions = [
39443951
[
39453952
'label' => $isUnpublishedDraft && $canSaveCanonical
39463953
? Craft::t('app', 'Create and continue editing')
39473954
: Craft::t('app', 'Save and continue editing'),
39483955
'redirect' => '{cpEditUrl}',
3956+
'params' => array_filter([
3957+
'redirectParams' => !empty($redirectParams) ? Json::encode($redirectParams) : null,
3958+
]),
39493959
'shortcut' => true,
39503960
'retainScroll' => true,
39513961
'eventData' => ['autosave' => false],
@@ -3962,7 +3972,10 @@ public function getAltActions(): array
39623972
'shortcut' => true,
39633973
'shift' => true,
39643974
'eventData' => ['autosave' => false],
3965-
'params' => ['addAnother' => 1],
3975+
'params' => [
3976+
'addAnother' => 1,
3977+
'returnUrl' => $returnUrl,
3978+
],
39663979
];
39673980
}
39683981

@@ -3987,6 +4000,7 @@ public function getAltActions(): array
39874000
'params' => [
39884001
'asUnpublishedDraft' => true,
39894002
'deleteProvisionalDraft' => true,
4003+
'redirectParams' => !empty($redirectParams) ? Json::encode($redirectParams) : null,
39904004
],
39914005
];
39924006
}

src/controllers/ElementIndexesController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,7 @@ protected function elementResponseData(bool $includeContainer, bool $includeActi
884884
[
885885
...$this->viewState,
886886
'fieldLayouts' => $this->fieldLayouts,
887+
'returnUrl' => $this->request->getParam('returnUrl'),
887888
],
888889
$this->sourceKey,
889890
$this->context,

src/controllers/ElementsController.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,19 @@ public function actionEdit(?ElementInterface $element, ?int $elementId = null):
364364
[$docTitle, $title] = $this->_editElementTitles($element);
365365
$enabledForSite = $element->getEnabledForSite();
366366
$hasRoute = $element->getRoute() !== null;
367-
$redirectUrl = $this->request->getValidatedQueryParam('returnUrl') ?? UrlHelper::cpReferralUrl() ?? ElementHelper::postEditUrl($element);
367+
368+
$redirectUrl = $this->request->getQueryParam('returnUrl');
369+
if ($redirectUrl) {
370+
// only require the URL to be hashed if it contains Twig code
371+
$validated = Craft::$app->getSecurity()->validateData($redirectUrl);
372+
if ($validated !== false) {
373+
$redirectUrl = $validated;
374+
} elseif (str_contains($redirectUrl, '{')) {
375+
throw new BadRequestHttpException("Invalid returnUrl param: $redirectUrl");
376+
}
377+
} else {
378+
$redirectUrl = ElementHelper::postEditUrl($element);
379+
}
368380

369381
// Site statuses
370382
if ($canEditMultipleSites) {
@@ -1040,9 +1052,16 @@ private function _additionalButtons(
10401052

10411053
// Revert content from this revision
10421054
if ($isRevision && $canSaveCanonical && $element->hasRevisions()) {
1055+
$returnUrl = $this->request->getQueryParam('returnUrl');
10431056
$components[] = Html::beginForm() .
10441057
Html::actionInput('elements/revert') .
10451058
Html::redirectInput('{cpEditUrl}') .
1059+
($returnUrl
1060+
? Html::hiddenInput('redirectParams', Json::encode([
1061+
'returnUrl' => $returnUrl,
1062+
]))
1063+
: ''
1064+
) .
10461065
Html::hiddenInput('elementId', (string)$canonical->id) .
10471066
Html::hiddenInput('revisionId', (string)$element->revisionId) .
10481067
Html::button(Craft::t('app', 'Revert content from this revision'), [
@@ -3012,6 +3031,11 @@ private function _asSuccess(
30123031
]);
30133032
}
30143033

3034+
$returnUrl = $this->request->getParam('returnUrl');
3035+
if ($returnUrl) {
3036+
$url = UrlHelper::urlWithParams($url, ['returnUrl' => $returnUrl]);
3037+
}
3038+
30153039
$response->redirect($url);
30163040
}
30173041

src/controllers/EntryTypesController.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
use craft\helpers\Cp;
2121
use craft\helpers\Html;
2222
use craft\helpers\StringHelper;
23-
use craft\helpers\UrlHelper;
2423
use craft\models\EntryType;
2524
use craft\models\Section;
2625
use craft\web\Controller;
@@ -125,7 +124,7 @@ public function actionEdit(?int $entryTypeId = null, ?EntryType $entryType = nul
125124
if (!$this->readOnly) {
126125
$response
127126
->action('entry-types/save')
128-
->redirectUrl(UrlHelper::cpReferralUrl() ?? 'settings/entry-types')
127+
->redirectUrl('settings/entry-types')
129128
->addAltAction(Craft::t('app', 'Save and continue editing'), [
130129
'redirect' => 'settings/entry-types/{id}',
131130
'shortcut' => true,

src/controllers/FieldsController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public function actionEditField(?int $fieldId = null, ?FieldInterface $field = n
205205
if (!$this->readOnly) {
206206
$response
207207
->action('fields/save-field')
208-
->redirectUrl(UrlHelper::cpReferralUrl() ?? 'settings/fields')
208+
->redirectUrl('settings/fields')
209209
->addAltAction(Craft::t('app', 'Save and continue editing'), [
210210
'redirect' => 'settings/fields/edit/{id}',
211211
'shortcut' => true,

src/helpers/Cp.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ public static function chipHtml(Chippable $component, array $config = []): strin
526526
* - `class` – Class name(s) that should be added to the container element
527527
* - `context` – The context the chip is going to be shown in (`index`, `field`, etc.)
528528
* - `hyperlink` – Whether the chip label should be hyperlinked to the element’s URL
529+
* - `returnUrl` – The `returnUrl` param that should be added to the hyperlink URL
529530
* - `id` – The chip’s `id` attribute
530531
* - `inputName` – The `name` attribute that should be set on a hidden input, if set
531532
* - `inputValue` – The `value` attribute that should be set on the hidden input, if `inputName` is set. Defaults to [[\craft\base\Identifiable::getId()`]].
@@ -555,6 +556,7 @@ public static function elementChipHtml(ElementInterface $element, array $config
555556
'attributes' => [],
556557
'autoReload' => true,
557558
'context' => 'index',
559+
'returnUrl' => null,
558560
'id' => sprintf('chip-%s', mt_rand()),
559561
'inputName' => null,
560562
'selectable' => false,
@@ -574,6 +576,7 @@ public static function elementChipHtml(ElementInterface $element, array $config
574576
'data' => array_filter([
575577
'settings' => $config['autoReload'] ? [
576578
'context' => $config['context'],
579+
'returnUrl' => $config['returnUrl'],
577580
'showDraftName' => $config['showDraftName'],
578581
'showProvisionalDraftLabel' => $config['showProvisionalDraftLabel'],
579582
] : false,
@@ -629,6 +632,7 @@ public static function elementChipHtml(ElementInterface $element, array $config
629632
* - `autoReload` – Whether the card should auto-reload itself when it’s saved
630633
* - `context` – The context the card is going to be shown in (`index`, `field`, etc.)
631634
* - `hyperlink` – Whether the card label should be hyperlinked to the element’s URL
635+
* - `returnUrl` – The `returnUrl` param that should be added to the hyperlink URL
632636
* - `id` – The card’s `id` attribute
633637
* - `inputName` – The `name` attribute that should be set on the hidden input, if `context` is set to `field`
634638
* - `selectable` – Whether the card should include a checkbox input
@@ -648,6 +652,7 @@ public static function elementCardHtml(ElementInterface $element, array $config
648652
'autoReload' => true,
649653
'context' => 'index',
650654
'hyperlink' => false,
655+
'returnUrl' => null,
651656
'id' => sprintf('card-%s', mt_rand()),
652657
'inputName' => null,
653658
'selectable' => false,
@@ -728,6 +733,7 @@ public static function elementCardHtml(ElementInterface $element, array $config
728733
'data' => array_filter([
729734
'settings' => $config['autoReload'] ? [
730735
'hyperlink' => $config['hyperlink'],
736+
'returnUrl' => $config['returnUrl'],
731737
'selectable' => $config['selectable'],
732738
'context' => $config['context'],
733739
'id' => Craft::$app->getView()->namespaceInputId($config['id']),
@@ -1154,6 +1160,16 @@ private static function elementLabelHtml(ElementInterface $element, array $confi
11541160
$config['context'] !== 'modal' &&
11551161
($url = $attributes['data']['cp-url'] ?? null)
11561162
) {
1163+
$returnUrl = $config['returnUrl'] ?? null;
1164+
if ($returnUrl) {
1165+
if (str_contains($returnUrl, '{')) {
1166+
$returnUrl = Craft::$app->getSecurity()->hashData($returnUrl);
1167+
}
1168+
$url = UrlHelper::urlWithParams($url, [
1169+
'returnUrl' => $returnUrl,
1170+
]);
1171+
}
1172+
11571173
$content = Html::tag('a', Html::tag('span', $content), [
11581174
'class' => ['label-link'],
11591175
'href' => $url,

src/helpers/UrlHelper.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ public static function cpHost(): string
579579
*
580580
* @return string|null
581581
* @since 5.9.0
582+
* @deprecated in 5.10.0
582583
*/
583584
public static function cpReferralUrl(): ?string
584585
{

src/templates/_elements/cardsview/elements.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{% set context = context ?? 'index' %}
22
{% set hyperlink = hyperlink ?? (context in ['index', 'embedded-index']) %}
3+
{% set returnUrl = returnUrl ?? null %}
34

45
{% apply spaceless %}
56
{% for element in elements %}
@@ -14,6 +15,7 @@
1415
selectable,
1516
sortable,
1617
hyperlink,
18+
returnUrl,
1719
}) }}
1820
{% endtag %}
1921
{% endfor %}

src/templates/_elements/tableview/elements.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
{% set showHeaderColumn = showHeaderColumn ?? false %}
99
{% set context = context ?? 'index' %}
1010
{% set hyperlink = hyperlink ?? (context in ['index', 'embedded-index']) %}
11+
{% set returnUrl = returnUrl ?? null %}
1112

1213
{% for element in elements %}
1314
{% set totalDescendants = structure
@@ -90,6 +91,7 @@
9091
class: ['chromeless'],
9192
},
9293
hyperlink,
94+
returnUrl,
9395
}) %}
9496
{% if not showHeaderColumn %}
9597
{% set chip = chip|attr({class: 'hide-label'}) %}

0 commit comments

Comments
 (0)