Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. ([#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))
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`. ([#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))
Expand Down
58 changes: 57 additions & 1 deletion src/Element/Operations/ElementWrites.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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']] ?? '')) {
Expand All @@ -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<string, CarbonInterface>
*/
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,
Expand Down
22 changes: 22 additions & 0 deletions tests/Feature/Element/ElementWrites/SaveElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down