From 71ea395ac3117a5e08deac628fa7bc9b366eb620 Mon Sep 17 00:00:00 2001 From: Brandon Kelly Date: Sat, 7 Nov 2026 22:18:22 +0000 Subject: [PATCH 1/2] =?UTF-8?q?Format=20dates=20in=20generated=20fields=20?= =?UTF-8?q?in=20the=20site=E2=80=99s=20locale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated field templates printed dates with PHP’s default string conversion: Carbon values as Y-m-d H:i:s, native date attributes as ISO 8601, and plain DateTime values threw an error. Dates referenced by a generated field template now print the way element cards show them, in the element’s site language, while filters still get a date. Amp-Thread-ID: https://ampcode.com/threads/T-01a1b7ff-b71a-7140-bea4-22eb1d71ca29 Co-authored-by: Amp --- CHANGELOG-WIP.md | 1 + CHANGELOG.md | 1 + src/Element/Operations/ElementWrites.php | 58 ++++++++++++++++++- .../Element/ElementWrites/SaveElementTest.php | 22 +++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index fbea49485ca..5b656c2ea76 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -129,6 +129,7 @@ - Table columns with invalid settings are now flagged in the Table field’s settings, and link setting validation errors in Link, Markdown, and Table fields now name the setting. ([#19998](https://github.com/craftcms/cms/pull/19998)) - Added support for configuring field layout field instruction positions. - Field layouts with a card view designer now show it and the content tab designer in “Content” and “Appearance” tabs, with Generated Fields in the Content tab. ([#20001](https://github.com/craftcms/cms/pull/20001)), ([#20003](https://github.com/craftcms/cms/pull/20003)) +- Generated fields now show dates in their site’s locale, the way element cards show them, when their template outputs a date directly, such as `{postDate}` or a Date field’s `{startDate}`. Filters still get the date, so `{startDate|date('Y')}` works as before. Generated fields are now also rendered in their site’s language, like entry title formats. - Entry types’ Icon and Color settings are now in their field layout designer’s Appearance tab, above Card Attributes. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Card view designers’ previews now reflect the owner’s color, icon, and name, such as an entry type’s, as they’re edited. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Entry type and user group descriptions, fields’ default instructions, tips, and warnings, and field layout elements’ instructions, tips, and warnings now have Markdown inputs. ([#20001](https://github.com/craftcms/cms/pull/20001)) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac413c39b96..e0eee67f322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -364,6 +364,7 @@ - Entry types’ Icon and Color settings are now in their field layout designer’s Appearance tab, above Card Attributes. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Card view designers’ previews now include a sample chip above the card, which reflects the owner’s color and whether its cards have thumbnails. ([#20003](https://github.com/craftcms/cms/pull/20003)) - Field layouts’ Generated Fields setting is now in the field layout designer’s Content tab. ([#20003](https://github.com/craftcms/cms/pull/20003)) +- Generated fields now show dates in their site’s locale, the way element cards show them, when their template outputs a date directly, such as `{postDate}` or a Date field’s `{startDate}`. Filters still get the date, so `{startDate|date('Y')}` works as before. Generated fields are now also rendered in their site’s language, like entry title formats. Existing values update the next time their elements are saved, such as with `php artisan craft:resave:all`. - Field layout element and dashboard widget width pickers now preview the new width on hover. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Added `CraftCms\Cms\FieldLayout\FieldLayout::getCardThumbStyle()`, `setCardThumbStyle()`, and `cardThumbStyle()`, and the `cardThumbStyle` field layout config key. ([#20148](https://github.com/craftcms/cms/pull/20148)) - Added `CraftCms\Cms\FieldLayout\FieldLayout::$thumbSources` and `thumbSources()`, and the `thumbSources` field layout config key, which replaces `thumbFieldKey`. ([#20156](https://github.com/craftcms/cms/discussions/20156), [#20181](https://github.com/craftcms/cms/pull/20181)) diff --git a/src/Element/Operations/ElementWrites.php b/src/Element/Operations/ElementWrites.php index ee5a802f6e5..55b729bea5f 100644 --- a/src/Element/Operations/ElementWrites.php +++ b/src/Element/Operations/ElementWrites.php @@ -4,6 +4,7 @@ namespace CraftCms\Cms\Element\Operations; +use Carbon\CarbonInterface; use CraftCms\Cms\Database\Table; use CraftCms\Cms\Element\Contracts\ElementInterface; use CraftCms\Cms\Element\Contracts\NestedElementInterface; @@ -32,6 +33,7 @@ use CraftCms\Cms\Element\Queries\Exceptions\ElementNotFoundException; use CraftCms\Cms\Element\Queries\Exceptions\QueryAbortedException; use CraftCms\Cms\Element\Validation\ElementRules; +use CraftCms\Cms\Field\Contracts\PreviewableFieldInterface; use CraftCms\Cms\Field\MissingField; use CraftCms\Cms\FieldLayout\FieldLayout; use CraftCms\Cms\Search\Search; @@ -40,14 +42,19 @@ use CraftCms\Cms\Support\Arr; use CraftCms\Cms\Support\DateTimeHelper; use CraftCms\Cms\Support\Facades\BulkOps; +use CraftCms\Cms\Support\Facades\I18N; use CraftCms\Cms\Support\Html; use CraftCms\Cms\Support\Json; use CraftCms\Cms\Support\Query; use CraftCms\Cms\Support\Str; +use CraftCms\Cms\Support\Typecast; use CraftCms\Cms\Support\Url; +use CraftCms\Cms\Translation\Locale; +use DateTimeInterface; use Exception; use Illuminate\Container\Attributes\Singleton; use Illuminate\Database\Query\Builder; +use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Throwable; @@ -115,9 +122,16 @@ public function completeGeneratedFields(ElementInterface $element): void } $generatedFieldValues = []; $updated = false; + $language = $siteElement->getSite()->getLanguage(); foreach ($generatedFields as $field) { - $value = renderObjectTemplate($field['template'] ?? '', $siteElement, escaperStrategy: 'html'); + $template = $field['template'] ?? ''; + $value = I18N::withLocale($language, $language, fn () => renderObjectTemplate( + $template, + $siteElement, + $this->generatedFieldDateVariables($siteElement, $template), + escaperStrategy: 'html', + )); $value = normalizeValue($value) ?? ''; if ($value !== ($content[$field['uid']] ?? '')) { @@ -141,6 +155,48 @@ public function completeGeneratedFields(ElementInterface $element): void } } + /** + * Returns the date values that a generated field template references, set to print + * in the current locale the way element cards show them. + * + * Passed as object template variables, they take precedence over the element’s own + * values for `{foo}` shorthand, while filters like `|date` still get a date. + * + * @return array + */ + private function generatedFieldDateVariables(ElementInterface $element, string $template): array + { + $variables = []; + $isReferenced = fn (string $name): bool => (bool) preg_match(sprintf('/\b%s\b/', preg_quote($name, '/')), $template); + $formatDatetime = fn (DateTimeInterface $date): string => I18N::getFormatter()->asDatetime($date, Locale::LENGTH_SHORT); + + foreach ($element->attributes() as $attribute) { + if ( + $isReferenced($attribute) && + Typecast::isDateTimeProperty($element::class, $attribute) && + ($value = $element->$attribute ?? null) instanceof DateTimeInterface + ) { + $variables[$attribute] = Date::instance($value)->settings(['toStringFormat' => $formatDatetime]); + } + } + + foreach ($element->getFieldLayout()?->getCustomFields() ?? [] as $field) { + if ( + ! isset($variables[$field->handle]) && + $isReferenced($field->handle) && + ($value = $element->getFieldValue($field->handle)) instanceof DateTimeInterface + ) { + $variables[$field->handle] = Date::instance($value)->settings([ + 'toStringFormat' => $field instanceof PreviewableFieldInterface + ? fn (DateTimeInterface $date): string => $field->getPreviewHtml($date, $element) + : $formatDatetime, + ]); + } + } + + return $variables; + } + public function saveElement( ElementInterface $element, bool $runValidation = true, diff --git a/tests/Feature/Element/ElementWrites/SaveElementTest.php b/tests/Feature/Element/ElementWrites/SaveElementTest.php index 9180c75ec07..26baae8a193 100644 --- a/tests/Feature/Element/ElementWrites/SaveElementTest.php +++ b/tests/Feature/Element/ElementWrites/SaveElementTest.php @@ -20,6 +20,7 @@ use CraftCms\Cms\Entry\Elements\Entry; use CraftCms\Cms\Entry\Models\Entry as EntryModel; use CraftCms\Cms\Entry\Models\EntryType; +use CraftCms\Cms\Field\Date; use CraftCms\Cms\Field\Matrix; use CraftCms\Cms\Field\Models\Field; use CraftCms\Cms\Field\PlainText; @@ -500,6 +501,27 @@ function createEntryWithPlainTextField(array $entryAttributes = []): array ->and(count(Event::getListeners(ElementLifecyclePropagated::class)))->toBe($listeners + 1); }); +it('formats dates that generated fields print in the site’s locale', function () { + Sites::getPrimarySite()->setLanguage('de'); + $entry = EntryModel::factory() + ->withField('startDate', Date::class, ['showTime' => true]) + ->withField('day', Date::class, ['showTime' => false]) + ->withField('endDate', Date::class) + ->createElementWithFields()->element; + $entry->getFieldLayout()->setGeneratedFields([[ + 'uid' => 'generated', + 'handle' => 'dates', + 'template' => '{startDate} | {day} | {postDate} | {startDate|date("Y")} | [{endDate}]', + ]]); + $entry->postDate = new DateTime('2026-03-04 05:06:00'); + $entry->setFieldValue('startDate', new DateTime('2026-10-09 10:00:00')); + $entry->setFieldValue('day', new DateTime('2027-02-28')); + + expect($this->writes->saveElement($entry, updateSearchIndex: false))->toBeTrue() + ->and($entry->getGeneratedFieldValues()['dates']) + ->toBe('09.10.2026, 10:00 PDT | 28.02.2027 | 04.03.2026, 05:06 | 2026 | []'); +}); + it('completes nested generated values across localized saves, duplicates, and merges', function () { $secondary = Site::factory()->create(); Sites::refreshSites(); From a981f6bd960e867771aa4260b055433c50ec4b6d Mon Sep 17 00:00:00 2001 From: Brandon Kelly Date: Sat, 7 Nov 2026 22:18:39 +0000 Subject: [PATCH 2/2] Link changelog entries to #20467 [ci skip] Amp-Thread-ID: https://ampcode.com/threads/T-01a1b7ff-b71a-7140-bea4-22eb1d71ca29 Co-authored-by: Amp --- CHANGELOG-WIP.md | 2 +- CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 5b656c2ea76..9028ccc17b0 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -129,7 +129,7 @@ - Table columns with invalid settings are now flagged in the Table field’s settings, and link setting validation errors in Link, Markdown, and Table fields now name the setting. ([#19998](https://github.com/craftcms/cms/pull/19998)) - Added support for configuring field layout field instruction positions. - Field layouts with a card view designer now show it and the content tab designer in “Content” and “Appearance” tabs, with Generated Fields in the Content tab. ([#20001](https://github.com/craftcms/cms/pull/20001)), ([#20003](https://github.com/craftcms/cms/pull/20003)) -- Generated fields now show dates in their site’s locale, the way element cards show them, when their template outputs a date directly, such as `{postDate}` or a Date field’s `{startDate}`. Filters still get the date, so `{startDate|date('Y')}` works as before. Generated fields are now also rendered in their site’s language, like entry title formats. +- Generated fields now show dates in their site’s locale, the way element cards show them, when their template outputs a date directly, such as `{postDate}` or a Date field’s `{startDate}`. Filters still get the date, so `{startDate|date('Y')}` works as before. Generated fields are now also rendered in their site’s language, like entry title formats. ([#20467](https://github.com/craftcms/cms/pull/20467)) - Entry types’ Icon and Color settings are now in their field layout designer’s Appearance tab, above Card Attributes. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Card view designers’ previews now reflect the owner’s color, icon, and name, such as an entry type’s, as they’re edited. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Entry type and user group descriptions, fields’ default instructions, tips, and warnings, and field layout elements’ instructions, tips, and warnings now have Markdown inputs. ([#20001](https://github.com/craftcms/cms/pull/20001)) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0eee67f322..63308a82bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -364,7 +364,7 @@ - Entry types’ Icon and Color settings are now in their field layout designer’s Appearance tab, above Card Attributes. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Card view designers’ previews now include a sample chip above the card, which reflects the owner’s color and whether its cards have thumbnails. ([#20003](https://github.com/craftcms/cms/pull/20003)) - Field layouts’ Generated Fields setting is now in the field layout designer’s Content tab. ([#20003](https://github.com/craftcms/cms/pull/20003)) -- Generated fields now show dates in their site’s locale, the way element cards show them, when their template outputs a date directly, such as `{postDate}` or a Date field’s `{startDate}`. Filters still get the date, so `{startDate|date('Y')}` works as before. Generated fields are now also rendered in their site’s language, like entry title formats. Existing values update the next time their elements are saved, such as with `php artisan craft:resave:all`. +- Generated fields now show dates in their site’s locale, the way element cards show them, when their template outputs a date directly, such as `{postDate}` or a Date field’s `{startDate}`. Filters still get the date, so `{startDate|date('Y')}` works as before. Generated fields are now also rendered in their site’s language, like entry title formats. Existing values update the next time their elements are saved, such as with `php artisan craft:resave:all`. ([#20467](https://github.com/craftcms/cms/pull/20467)) - Field layout element and dashboard widget width pickers now preview the new width on hover. ([#20001](https://github.com/craftcms/cms/pull/20001)) - Added `CraftCms\Cms\FieldLayout\FieldLayout::getCardThumbStyle()`, `setCardThumbStyle()`, and `cardThumbStyle()`, and the `cardThumbStyle` field layout config key. ([#20148](https://github.com/craftcms/cms/pull/20148)) - Added `CraftCms\Cms\FieldLayout\FieldLayout::$thumbSources` and `thumbSources()`, and the `thumbSources` field layout config key, which replaces `thumbFieldKey`. ([#20156](https://github.com/craftcms/cms/discussions/20156), [#20181](https://github.com/craftcms/cms/pull/20181))