From 43dc86d3ba361cd5e55737022335e1ff04f316c3 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Fri, 17 Feb 2023 14:08:01 +1300 Subject: [PATCH 01/16] API Standardise deprecations --- _config.php | 5 ----- src/Extension/FluentExtension.php | 14 +++++++++----- 2 files changed, 9 insertions(+), 10 deletions(-) delete mode 100644 _config.php diff --git a/_config.php b/_config.php deleted file mode 100644 index e1f5792a..00000000 --- a/_config.php +++ /dev/null @@ -1,5 +0,0 @@ -LocaleInformation($locale)->getLinkingMode(); } @@ -949,11 +949,11 @@ public function BaseURLForLocale($locale) /** * @param string $locale * @return string - * @deprecated Use LocaleInformation instead + * @deprecated 5.0.0 Use LocaleInformation() instead */ public function LocaleLink($locale) { - Deprecation::notice('5.0', 'Use LocaleInformation instead'); + Deprecation::notice('5.0.0', 'Use LocaleInformation() instead'); return $this->LocaleInformation($locale)->getLink(); } @@ -1021,7 +1021,11 @@ protected function requireSavedInLocale() } if ($this->owner->config()->get('cms_publish_required') !== null) { - Deprecation::notice('5.0', 'Use cms_localisation_required instead'); + Deprecation::notice( + '5.0.0', + 'The "' . self::class . '.cms_publish_required" configuration option is deprecated. Use "' + . self::class . '.cms_localisation_required instead' + ); return $this->owner->config()->get('cms_publish_required'); } From f07b03663d3acdbfd55bb9f8f2934ab8680757a9 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Wed, 24 May 2023 09:02:59 +1200 Subject: [PATCH 02/16] FIX Don't try to preview unlocalised objects (#781) --- src/Extension/FluentExtension.php | 16 ++++++++++++++++ src/Middleware/InitStateMiddleware.php | 5 ----- tests/php/Middleware/InitStateMiddlewareTest.php | 3 ++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Extension/FluentExtension.php b/src/Extension/FluentExtension.php index 69b2cc57..91bb9a1e 100644 --- a/src/Extension/FluentExtension.php +++ b/src/Extension/FluentExtension.php @@ -1136,6 +1136,22 @@ public function updateFluentCMSField(FormField $field) $field->setTitle($tooltip); } + /** + * Update preview link to null if the object isn't in the current locale + * and we can't fallback cleanly. + * + * @param ?string $link + */ + public function updatePreviewLink(&$link): void + { + $owner = $this->owner; + $info = $owner->LocaleInformation(FluentState::singleton()->getLocale()); + + if (!$info->getSourceLocale()) { + $link = null; + } + } + /** * Require that this record is saved in the given locale for it to be visible * diff --git a/src/Middleware/InitStateMiddleware.php b/src/Middleware/InitStateMiddleware.php index e30ac8c5..49319396 100644 --- a/src/Middleware/InitStateMiddleware.php +++ b/src/Middleware/InitStateMiddleware.php @@ -70,11 +70,6 @@ public function getIsFrontend(HTTPRequest $request) } } - // If using the CMS preview, do not treat the site as frontend - if ($request->getVar('CMSPreview')) { - return false; - } - return true; } diff --git a/tests/php/Middleware/InitStateMiddlewareTest.php b/tests/php/Middleware/InitStateMiddlewareTest.php index c2decf57..3989c35b 100644 --- a/tests/php/Middleware/InitStateMiddlewareTest.php +++ b/tests/php/Middleware/InitStateMiddlewareTest.php @@ -35,7 +35,8 @@ public function isFrontendProvider() ['/', [], true], ['foo', [], true], ['my-blog/my-post', [], true], - ['my-blog/my-post', ['CMSPreview' => 1], false], + // CMS preview is front-end, and if there's no localised copy the PreviewLink will be null + ['my-blog/my-post', ['CMSPreview' => 1], true], ]; } } From 034dd15197ada07594436d4d337246eeda734a36 Mon Sep 17 00:00:00 2001 From: mikey-harveycameron <117692712+mikey-harveycameron@users.noreply.github.com> Date: Wed, 31 May 2023 13:50:29 +1000 Subject: [PATCH 03/16] Update FluentExtension.php skip empty 'where' clauses --- src/Extension/FluentExtension.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Extension/FluentExtension.php b/src/Extension/FluentExtension.php index 69b2cc57..ae2e3a15 100644 --- a/src/Extension/FluentExtension.php +++ b/src/Extension/FluentExtension.php @@ -583,6 +583,10 @@ public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) // Apply substitutions $localisedPredicate = str_replace($conditionSearch, $conditionReplace, $predicate); + if (empty($localisedPredicate)) { + continue; + } + $where[$index] = [ $localisedPredicate => $parameters ]; From 336dca26d2cf0bb040cc028b005ba0c369fc21ce Mon Sep 17 00:00:00 2001 From: Remy Vaartjes Date: Fri, 16 Jun 2023 15:01:04 +0200 Subject: [PATCH 04/16] Implement onAfterDuplicate Also copy translations on duplicate --- src/Extension/FluentExtension.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Extension/FluentExtension.php b/src/Extension/FluentExtension.php index 45793451..d1ec6853 100644 --- a/src/Extension/FluentExtension.php +++ b/src/Extension/FluentExtension.php @@ -629,6 +629,32 @@ public function onAfterWrite(): void { $this->handleClassChanged(); } + + /** + * If an object is duplicated also duplicate existing localised values from original to new object. + */ + public function onAfterDuplicate($original, $doWrite, $relations): void + { + $localisedTables = $this->owner->getLocalisedTables(); + foreach ($localisedTables as $tableName => $fields) { + // Target IDs + $fromID = $original->ID; + $toID = $this->owner->ID; + + // Get localised table + $localisedTable = $this->getLocalisedTable($tableName); + + // Remove existing translations from duplicated object + DB::prepared_query("DELETE FROM \"$localisedTable\" WHERE \"RecordID\" = ?", [$toID]); + + // Copy translations to duplicated object + $fields_str = '"' . implode('","', $fields) . '"'; + DB::prepared_query("INSERT INTO \"$localisedTable\" ( \"RecordID\", \"Locale\", $fields_str) + SELECT ? AS \"RecordID\", \"Locale\", $fields_str + FROM \"$localisedTable\" + WHERE \"RecordID\" = ?", [$toID, $fromID]); + } + } /** * If an object is changed to another class, we should trigger localised copy From 43b4214b1c1b16faf4c5d1a89155af27649654e3 Mon Sep 17 00:00:00 2001 From: Remy Vaartjes Date: Fri, 16 Jun 2023 15:22:13 +0200 Subject: [PATCH 05/16] Add onAfterDuplicate to FluentVersionedExtension --- src/Extension/FluentVersionedExtension.php | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/Extension/FluentVersionedExtension.php b/src/Extension/FluentVersionedExtension.php index c5c1789a..4d766384 100644 --- a/src/Extension/FluentVersionedExtension.php +++ b/src/Extension/FluentVersionedExtension.php @@ -1012,4 +1012,51 @@ protected function setVersionCacheItem(string $class, string $stage, string $loc // Internally store nulls as 0 $this->versionsCache[$class][$stage][$locale][$key] = $value ?: 0; } + + /** + * If an object is duplicated also duplicate existing localised values from original to new object. + */ + public function onAfterDuplicate($original, $doWrite, $relations): void + { + parent::onAfterDuplicate($original, $doWrite, $relations); + + $localisedTables = $this->owner->getLocalisedTables(); + foreach ($localisedTables as $tableName => $fields) { + // Target IDs + $fromID = $original->ID; + $toID = $this->owner->ID; + + // Get localised table + $localisedTable = $this->getLocalisedTable($tableName) . self::SUFFIX_VERSIONS; + + // Remove existing translation versions from duplicated object + DB::prepared_query("DELETE FROM \"$localisedTable\" WHERE \"RecordID\" = ?", [$toID]); + + // Copy translations to duplicated object + $localisedFields = array_merge(['Locale', 'Version'], $fields); + $fields_str = '"' . implode('","', $localisedFields) . '"'; + + // Copy all versions of localised object + DB::prepared_query("INSERT INTO \"$localisedTable\" ( \"RecordID\", $fields_str) + SELECT ? AS \"RecordID\", $fields_str + FROM \"$localisedTable\" + WHERE \"RecordID\" = ?", [$toID, $fromID]); + + // Also copy versions of base record + $versionsTableName = $tableName . self::SUFFIX_VERSIONS; + + // Remove existing versions from duplicated object, created by onBeforeWrite + DB::prepared_query("DELETE FROM \"$versionsTableName\" WHERE \"RecordID\" = ?", [$toID]); + + // Copy all versions of base record, todo: optimize to only copy needed versions + $fields = DB::query("SELECT \"COLUMN_NAME\" FROM \"INFORMATION_SCHEMA\".\"COLUMNS\" WHERE \"TABLE_NAME\" = '$versionsTableName' AND \"COLUMN_NAME\" NOT IN('ID','RecordID')"); + $fields_str = '"' . implode('","', $fields->column()) . '"'; + DB::prepared_query("INSERT INTO \"$versionsTableName\" ( \"RecordID\", $fields_str) + SELECT ? AS \"RecordID\", $fields_str + FROM \"$versionsTableName\" + WHERE \"RecordID\" = ?", [$toID, $fromID]); + + } + } + } From 9d04d7a2fbbb087d1f36fe955d15abf062fe9b42 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 1 Sep 2023 09:38:13 +1200 Subject: [PATCH 06/16] feat: add absolute base url for locale --- src/Model/Locale.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Model/Locale.php b/src/Model/Locale.php index 40b0eff3..72103eec 100644 --- a/src/Model/Locale.php +++ b/src/Model/Locale.php @@ -586,6 +586,16 @@ public function getBaseURL() return $base; } + /** + * Absolute base url + * + * @return false|string + */ + public function getAbsoluteBaseURL() + { + return Director::absoluteURL($this->getBaseURL()); + } + /** * Get other locales that appear alongside this (including self) * From 904e3f450d4b21e5a8b9bf5ce652290556027985 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 1 Sep 2023 12:06:03 +1200 Subject: [PATCH 07/16] chore: fix linting --- src/Extension/FluentVersionedExtension.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Extension/FluentVersionedExtension.php b/src/Extension/FluentVersionedExtension.php index 4d766384..4eaf1805 100644 --- a/src/Extension/FluentVersionedExtension.php +++ b/src/Extension/FluentVersionedExtension.php @@ -1012,8 +1012,8 @@ protected function setVersionCacheItem(string $class, string $stage, string $loc // Internally store nulls as 0 $this->versionsCache[$class][$stage][$locale][$key] = $value ?: 0; } - - /** + + /** * If an object is duplicated also duplicate existing localised values from original to new object. */ public function onAfterDuplicate($original, $doWrite, $relations): void @@ -1055,8 +1055,6 @@ public function onAfterDuplicate($original, $doWrite, $relations): void SELECT ? AS \"RecordID\", $fields_str FROM \"$versionsTableName\" WHERE \"RecordID\" = ?", [$toID, $fromID]); - } } - } From 9ab08d10516aa0d5350ff9727992ddccd8d90922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmlindenhofer=E2=80=9D?= Date: Tue, 14 Nov 2023 10:42:21 +0100 Subject: [PATCH 08/16] copy Bugfix from here https://github.com/tractorcow-farm/silverstripe-fluent/pull/793 to Version 6.0 --- src/Extension/FluentExtension.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Extension/FluentExtension.php b/src/Extension/FluentExtension.php index 69b2cc57..7aa7f354 100644 --- a/src/Extension/FluentExtension.php +++ b/src/Extension/FluentExtension.php @@ -583,6 +583,10 @@ public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null) // Apply substitutions $localisedPredicate = str_replace($conditionSearch, $conditionReplace, $predicate); + if (empty($localisedPredicate)) { + continue; + } + $where[$index] = [ $localisedPredicate => $parameters ]; From f18bc716ff94f1f7b55eb4bd7b5ac7d9a388fb76 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Wed, 15 Nov 2023 12:13:18 +1300 Subject: [PATCH 09/16] fix: allow plugins --- composer.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f0bd5a00..30aa48ff 100644 --- a/composer.json +++ b/composer.json @@ -64,5 +64,10 @@ "lint-clean": "phpcbf src/ tests/php/" }, "prefer-stable": true, - "minimum-stability": "dev" + "minimum-stability": "dev", + "config": { + "allow-plugins": { + "composer/installers": true + } + } } From 14ecdb1f6b03328f76c7c8adfe2eb6e222725e56 Mon Sep 17 00:00:00 2001 From: David Toews Date: Thu, 8 Feb 2024 09:41:16 -0700 Subject: [PATCH 10/16] FIX allows i18nTextCollector to concatenate keys fixes #823 - The magic constant __TRAIT__ was inserting namespaces into localization keys, containing slashes that could not be parsed by the TextCollector task. These have been replaced w/ the Trait name as a string --- src/Extension/Traits/FluentAdminTrait.php | 36 +++++++++++----------- src/Extension/Traits/FluentBadgeTrait.php | 6 ++-- src/Extension/Traits/FluentObjectTrait.php | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Extension/Traits/FluentAdminTrait.php b/src/Extension/Traits/FluentAdminTrait.php index 269cf5e7..f9d642d1 100644 --- a/src/Extension/Traits/FluentAdminTrait.php +++ b/src/Extension/Traits/FluentAdminTrait.php @@ -107,7 +107,7 @@ protected function updateFluentActions( // Add menu button $moreOptions = Tab::create( 'FluentMenuOptions', - _t(__TRAIT__ . '.Localisation', 'Localisation') + _t('FluentAdminTrait.Localisation', 'Localisation') ); $moreOptions->addExtraClass('popover-actions-simulate'); $rootTabSet->push($moreOptions); @@ -117,7 +117,7 @@ protected function updateFluentActions( FormAction::create( 'clearFluent', _t( - __TRAIT__ . '.Label_clearFluent', + 'FluentAdminTrait.Label_clearFluent', "Clear from all except '{title}'", [ 'title' => $locale->getTitle() @@ -129,7 +129,7 @@ protected function updateFluentActions( FormAction::create( 'copyFluent', _t( - __TRAIT__ . '.Label_copyFluent', + 'FluentAdminTrait.Label_copyFluent', "Copy '{title}' to other locales", [ 'title' => $locale->getTitle() @@ -144,7 +144,7 @@ protected function updateFluentActions( FormAction::create( 'unpublishFluent', _t( - __TRAIT__ . '.Label_unpublishFluent', + 'FluentAdminTrait.Label_unpublishFluent', 'Unpublish (all locales)' ) )->addExtraClass('btn-secondary') @@ -153,7 +153,7 @@ protected function updateFluentActions( FormAction::create( 'archiveFluent', _t( - __TRAIT__ . '.Label_archiveFluent', + 'FluentAdminTrait.Label_archiveFluent', 'Unpublish and Archive (all locales)' ) )->addExtraClass('btn-outline-danger') @@ -162,7 +162,7 @@ protected function updateFluentActions( FormAction::create( 'publishFluent', _t( - __TRAIT__ . '.Label_publishFluent', + 'FluentAdminTrait.Label_publishFluent', 'Save & Publish (all locales)' ) )->addExtraClass('btn-primary') @@ -172,7 +172,7 @@ protected function updateFluentActions( FormAction::create( 'deleteFluent', _t( - __TRAIT__ . '.Label_deleteFluent', + 'FluentAdminTrait.Label_deleteFluent', 'Delete (all locales)' ) )->addExtraClass('btn-outline-danger') @@ -187,7 +187,7 @@ protected function updateFluentActions( FormAction::create( 'hideFluent', _t( - __TRAIT__ . '.Label_hideFluent', + 'FluentAdminTrait.Label_hideFluent', "Hide from '{title}'", [ 'title' => $locale->getTitle() @@ -200,7 +200,7 @@ protected function updateFluentActions( FormAction::create( 'showFluent', _t( - __TRAIT__ . '.Label_showFluent', + 'FluentAdminTrait.Label_showFluent', "Show in '{title}'", [ 'title' => $locale->getTitle() @@ -256,7 +256,7 @@ public function clearFluent($data, $form) }); $message = _t( - __TRAIT__ . '.ClearAllNotice', + 'FluentAdminTrait.ClearAllNotice', "All localisations have been cleared for '{title}'.", ['title' => $record->Title] ); @@ -292,7 +292,7 @@ public function copyFluent($data, $form) if ($locale->ID == $originalLocale->ID) { return; } - + if ($record->hasExtension(Versioned::class)) { $record->writeToStage(Versioned::DRAFT); } else { @@ -302,7 +302,7 @@ public function copyFluent($data, $form) }); $message = _t( - __TRAIT__ . '.CopyNotice', + 'FluentAdminTrait.CopyNotice', "Copied '{title}' to all other locales.", ['title' => $record->Title] ); @@ -336,7 +336,7 @@ public function unpublishFluent($data, $form) }); $message = _t( - __TRAIT__ . '.UnpublishNotice', + 'FluentAdminTrait.UnpublishNotice', "Unpublished '{title}' from all locales.", ['title' => $record->Title] ); @@ -386,7 +386,7 @@ public function archiveFluent($data, $form) $policy->delete($record); $message = _t( - __TRAIT__ . '.ArchiveNotice', + 'FluentAdminTrait.ArchiveNotice', "Archived '{title}' and all of its localisations.", ['title' => $record->Title] ); @@ -434,7 +434,7 @@ public function deleteFluent($data, $form) $policy->delete($record); $message = _t( - __TRAIT__ . '.DeleteNotice', + 'FluentAdminTrait.DeleteNotice', "Deleted '{title}' and all of its localisations.", ['title' => $record->Title] ); @@ -478,7 +478,7 @@ public function publishFluent($data, $form) }); $message = _t( - __TRAIT__ . '.PublishNotice', + 'FluentAdminTrait.PublishNotice', "Published '{title}' across all locales.", ['title' => $record->Title] ); @@ -508,7 +508,7 @@ public function showFluent($data, $form) $record->FilteredLocales()->add($locale); $message = _t( - __TRAIT__ . '.ShowNotice', + 'FluentAdminTrait.ShowNotice', "Record '{title}' is now visible in {locale}", [ 'title' => $record->Title, @@ -541,7 +541,7 @@ public function hideFluent($data, $form) $record->FilteredLocales()->remove($locale); $message = _t( - __TRAIT__ . '.HideNotice', + 'FluentAdminTrait.HideNotice', "Record '{title}' is now hidden in {locale}", [ 'title' => $record->Title, diff --git a/src/Extension/Traits/FluentBadgeTrait.php b/src/Extension/Traits/FluentBadgeTrait.php index 8666725b..0845f0ab 100644 --- a/src/Extension/Traits/FluentBadgeTrait.php +++ b/src/Extension/Traits/FluentBadgeTrait.php @@ -76,7 +76,7 @@ protected function generateBadgeHTML( // If the object has been localised in the current locale, show a "localised" state $badgeClasses[] = 'fluent-badge--default'; $tooltip = _t( - __TRAIT__ . '.BadgeLocalised', + 'FluentBadgeTrait.BadgeLocalised', 'Localised in {locale}', [ 'locale' => $locale->getTitle() @@ -86,7 +86,7 @@ protected function generateBadgeHTML( // If object is inheriting content from another locale show the source $badgeClasses[] = 'fluent-badge--localised'; $tooltip = _t( - __TRAIT__ . '.BadgeInherited', + 'FluentBadgeTrait.BadgeInherited', 'Inherited from {locale}', [ 'locale' => $info->getSourceLocale()->getTitle() @@ -97,7 +97,7 @@ protected function generateBadgeHTML( // by either localising or seting up a locale fallback $badgeClasses[] = 'fluent-badge--invisible'; $tooltip = _t( - __TRAIT__ . '.BaggeInvisible', + 'FluentBadgeTrait.BaggeInvisible', '{type} has no available content in {locale}, localise the {type} or provide a locale fallback', [ 'type' => $record->i18n_singular_name(), diff --git a/src/Extension/Traits/FluentObjectTrait.php b/src/Extension/Traits/FluentObjectTrait.php index 3ba8139d..248afa03 100644 --- a/src/Extension/Traits/FluentObjectTrait.php +++ b/src/Extension/Traits/FluentObjectTrait.php @@ -128,7 +128,7 @@ protected function updateFluentCMSFields(FieldList $fields) $fields ->fieldByName('Root.Locales') - ->setTitle(_t(__TRAIT__ . '.TAB_LOCALISATION', 'Localisation')); + ->setTitle(_t('FluentObjectTrait.TAB_LOCALISATION', 'Localisation')); } else { $fields->push($gridField); } From 2a8e81ef37f33d3e04c5fa35ac0020ecdfbb6a62 Mon Sep 17 00:00:00 2001 From: David Toews Date: Thu, 8 Feb 2024 17:09:57 -0700 Subject: [PATCH 11/16] Adds full namespace to string literal --- src/Extension/Traits/FluentAdminTrait.php | 34 +++++++++++----------- src/Extension/Traits/FluentBadgeTrait.php | 6 ++-- src/Extension/Traits/FluentObjectTrait.php | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Extension/Traits/FluentAdminTrait.php b/src/Extension/Traits/FluentAdminTrait.php index f9d642d1..63da66a3 100644 --- a/src/Extension/Traits/FluentAdminTrait.php +++ b/src/Extension/Traits/FluentAdminTrait.php @@ -107,7 +107,7 @@ protected function updateFluentActions( // Add menu button $moreOptions = Tab::create( 'FluentMenuOptions', - _t('FluentAdminTrait.Localisation', 'Localisation') + _t('TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Localisation', 'Localisation') ); $moreOptions->addExtraClass('popover-actions-simulate'); $rootTabSet->push($moreOptions); @@ -117,7 +117,7 @@ protected function updateFluentActions( FormAction::create( 'clearFluent', _t( - 'FluentAdminTrait.Label_clearFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_clearFluent', "Clear from all except '{title}'", [ 'title' => $locale->getTitle() @@ -129,7 +129,7 @@ protected function updateFluentActions( FormAction::create( 'copyFluent', _t( - 'FluentAdminTrait.Label_copyFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_copyFluent', "Copy '{title}' to other locales", [ 'title' => $locale->getTitle() @@ -144,7 +144,7 @@ protected function updateFluentActions( FormAction::create( 'unpublishFluent', _t( - 'FluentAdminTrait.Label_unpublishFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_unpublishFluent', 'Unpublish (all locales)' ) )->addExtraClass('btn-secondary') @@ -153,7 +153,7 @@ protected function updateFluentActions( FormAction::create( 'archiveFluent', _t( - 'FluentAdminTrait.Label_archiveFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_archiveFluent', 'Unpublish and Archive (all locales)' ) )->addExtraClass('btn-outline-danger') @@ -162,7 +162,7 @@ protected function updateFluentActions( FormAction::create( 'publishFluent', _t( - 'FluentAdminTrait.Label_publishFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_publishFluent', 'Save & Publish (all locales)' ) )->addExtraClass('btn-primary') @@ -172,7 +172,7 @@ protected function updateFluentActions( FormAction::create( 'deleteFluent', _t( - 'FluentAdminTrait.Label_deleteFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_deleteFluent', 'Delete (all locales)' ) )->addExtraClass('btn-outline-danger') @@ -187,7 +187,7 @@ protected function updateFluentActions( FormAction::create( 'hideFluent', _t( - 'FluentAdminTrait.Label_hideFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_hideFluent', "Hide from '{title}'", [ 'title' => $locale->getTitle() @@ -200,7 +200,7 @@ protected function updateFluentActions( FormAction::create( 'showFluent', _t( - 'FluentAdminTrait.Label_showFluent', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.Label_showFluent', "Show in '{title}'", [ 'title' => $locale->getTitle() @@ -256,7 +256,7 @@ public function clearFluent($data, $form) }); $message = _t( - 'FluentAdminTrait.ClearAllNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.ClearAllNotice', "All localisations have been cleared for '{title}'.", ['title' => $record->Title] ); @@ -302,7 +302,7 @@ public function copyFluent($data, $form) }); $message = _t( - 'FluentAdminTrait.CopyNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.CopyNotice', "Copied '{title}' to all other locales.", ['title' => $record->Title] ); @@ -336,7 +336,7 @@ public function unpublishFluent($data, $form) }); $message = _t( - 'FluentAdminTrait.UnpublishNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.UnpublishNotice', "Unpublished '{title}' from all locales.", ['title' => $record->Title] ); @@ -386,7 +386,7 @@ public function archiveFluent($data, $form) $policy->delete($record); $message = _t( - 'FluentAdminTrait.ArchiveNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.ArchiveNotice', "Archived '{title}' and all of its localisations.", ['title' => $record->Title] ); @@ -434,7 +434,7 @@ public function deleteFluent($data, $form) $policy->delete($record); $message = _t( - 'FluentAdminTrait.DeleteNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.DeleteNotice', "Deleted '{title}' and all of its localisations.", ['title' => $record->Title] ); @@ -478,7 +478,7 @@ public function publishFluent($data, $form) }); $message = _t( - 'FluentAdminTrait.PublishNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.PublishNotice', "Published '{title}' across all locales.", ['title' => $record->Title] ); @@ -508,7 +508,7 @@ public function showFluent($data, $form) $record->FilteredLocales()->add($locale); $message = _t( - 'FluentAdminTrait.ShowNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.ShowNotice', "Record '{title}' is now visible in {locale}", [ 'title' => $record->Title, @@ -541,7 +541,7 @@ public function hideFluent($data, $form) $record->FilteredLocales()->remove($locale); $message = _t( - 'FluentAdminTrait.HideNotice', + 'TractorCow\Fluent\Extension\Traits\FluentAdminTrait.HideNotice', "Record '{title}' is now hidden in {locale}", [ 'title' => $record->Title, diff --git a/src/Extension/Traits/FluentBadgeTrait.php b/src/Extension/Traits/FluentBadgeTrait.php index 0845f0ab..c6146d8f 100644 --- a/src/Extension/Traits/FluentBadgeTrait.php +++ b/src/Extension/Traits/FluentBadgeTrait.php @@ -76,7 +76,7 @@ protected function generateBadgeHTML( // If the object has been localised in the current locale, show a "localised" state $badgeClasses[] = 'fluent-badge--default'; $tooltip = _t( - 'FluentBadgeTrait.BadgeLocalised', + 'TractorCow\Fluent\Extension\Traits\FluentBadgeTrait.BadgeLocalised', 'Localised in {locale}', [ 'locale' => $locale->getTitle() @@ -86,7 +86,7 @@ protected function generateBadgeHTML( // If object is inheriting content from another locale show the source $badgeClasses[] = 'fluent-badge--localised'; $tooltip = _t( - 'FluentBadgeTrait.BadgeInherited', + 'TractorCow\Fluent\Extension\Traits\FluentBadgeTrait.BadgeInherited', 'Inherited from {locale}', [ 'locale' => $info->getSourceLocale()->getTitle() @@ -97,7 +97,7 @@ protected function generateBadgeHTML( // by either localising or seting up a locale fallback $badgeClasses[] = 'fluent-badge--invisible'; $tooltip = _t( - 'FluentBadgeTrait.BaggeInvisible', + 'TractorCow\Fluent\Extension\Traits\FluentBadgeTrait.BaggeInvisible', '{type} has no available content in {locale}, localise the {type} or provide a locale fallback', [ 'type' => $record->i18n_singular_name(), diff --git a/src/Extension/Traits/FluentObjectTrait.php b/src/Extension/Traits/FluentObjectTrait.php index 248afa03..aae4cd7a 100644 --- a/src/Extension/Traits/FluentObjectTrait.php +++ b/src/Extension/Traits/FluentObjectTrait.php @@ -128,7 +128,7 @@ protected function updateFluentCMSFields(FieldList $fields) $fields ->fieldByName('Root.Locales') - ->setTitle(_t('FluentObjectTrait.TAB_LOCALISATION', 'Localisation')); + ->setTitle(_t('TractorCow\Fluent\Extension\Traits\FluentObjectTrait.TAB_LOCALISATION', 'Localisation')); } else { $fields->push($gridField); } From f79f0f90174f1b1ae49bd4656dc2accd7a2add62 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 21 Mar 2024 09:25:03 +1300 Subject: [PATCH 12/16] fix: don't invoke db for ready check outside of dev urls fixes #828 --- src/Model/CachableModel.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Model/CachableModel.php b/src/Model/CachableModel.php index 1c709b9d..b24dc8ae 100644 --- a/src/Model/CachableModel.php +++ b/src/Model/CachableModel.php @@ -2,6 +2,8 @@ namespace TractorCow\Fluent\Model; +use SilverStripe\Control\Director; +use SilverStripe\Control\HTTPRequest; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; @@ -69,6 +71,13 @@ public static function clearCached() */ protected static function databaseIsReady() { + // Outside of dev/ don't actually do any checks, assume ready + /** @var HTTPRequest $request */ + $request = Injector::inst()->get(HTTPRequest::class); + if (stripos($request->getURL(false), 'dev/') !== 0) { + return true; + } + $object = DataObject::singleton(static::class); // if any of the tables aren't created in the database From 05ef5774130a5102d7e2fd089516536745850900 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 21 Mar 2024 09:32:33 +1300 Subject: [PATCH 13/16] fix: redundant import --- src/Model/CachableModel.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Model/CachableModel.php b/src/Model/CachableModel.php index b24dc8ae..c005deec 100644 --- a/src/Model/CachableModel.php +++ b/src/Model/CachableModel.php @@ -2,7 +2,6 @@ namespace TractorCow\Fluent\Model; -use SilverStripe\Control\Director; use SilverStripe\Control\HTTPRequest; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; From 8985d8c8db9f69ae7d1c9e44bb3e1b122c94f074 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Mon, 25 Mar 2024 11:08:46 +1300 Subject: [PATCH 14/16] fix: check for request prior to access --- src/Model/CachableModel.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Model/CachableModel.php b/src/Model/CachableModel.php index c005deec..394c1637 100644 --- a/src/Model/CachableModel.php +++ b/src/Model/CachableModel.php @@ -71,10 +71,12 @@ public static function clearCached() protected static function databaseIsReady() { // Outside of dev/ don't actually do any checks, assume ready - /** @var HTTPRequest $request */ - $request = Injector::inst()->get(HTTPRequest::class); - if (stripos($request->getURL(false), 'dev/') !== 0) { - return true; + if (Injector::inst()->has(HTTPRequest::class)) { + /** @var HTTPRequest $request */ + $request = Injector::inst()->get(HTTPRequest::class); + if (stripos($request->getURL(false), 'dev/') !== 0) { + return true; + } } $object = DataObject::singleton(static::class); From 9aea31d68c63b7b834afb4140678aec95db1bdfd Mon Sep 17 00:00:00 2001 From: "Nathan J. Brauer" Date: Tue, 26 Mar 2024 21:22:15 -0700 Subject: [PATCH 15/16] NEW Added dev/task to initialise DataObjects for existing datasets New task is based on the original task built for SiteTree but is now extendable for any DataObject enabled with Fluent. To prevent duplicate code, the original task now extends the new task. --- .../InitialDataObjectLocalisationTask.php | 287 ++++++++++++++++++ src/Task/InitialPageLocalisationTask.php | 108 ++----- 2 files changed, 308 insertions(+), 87 deletions(-) create mode 100644 src/Task/InitialDataObjectLocalisationTask.php diff --git a/src/Task/InitialDataObjectLocalisationTask.php b/src/Task/InitialDataObjectLocalisationTask.php new file mode 100644 index 00000000..a8897e73 --- /dev/null +++ b/src/Task/InitialDataObjectLocalisationTask.php @@ -0,0 +1,287 @@ +exclude_classes`. + * @var string[] + */ + protected $include_only_classes = []; + + /** + * When extending this class, you may choose to exclude these specific classes. + * This is IGNORED if `$this->include_only_classes` is not empty. + * @var string[] + */ + protected $exclude_classes = [ + SiteTree::class + ]; + + /** + * @param HTTPRequest $request + * @return void + * @throws \ReflectionException + * @throws \SilverStripe\ORM\ValidationException + */ + public function run($request) + { + if (!Director::is_cli()) { + echo '
' . PHP_EOL;
+        }
+
+        $publish = (bool)$request->getVar('publish');
+        $limit = (int)$request->getVar('limit');
+
+        $total_results = [
+            'localisable' => 0,
+            'localised' => 0,
+            'publishable' => 0,
+            'published' => 0,
+        ];
+
+        /** @var Locale $globalLocale */
+        $globalLocale = Locale::get()
+            ->filter(['IsGlobalDefault' => 1])
+            ->sort('ID', 'ASC')
+            ->first();
+
+        if (!$globalLocale) {
+            echo 'Please set global locale first!' . PHP_EOL;
+
+            return;
+        }
+
+        if ($this->include_only_classes && is_array($this->include_only_classes)) {
+            $classesWithFluent = $this->include_only_classes;
+            foreach ($this->include_only_classes as $key => $dataClass) {
+                if (!$this->isClassNamePermitted($dataClass)) {
+                    echo sprintf('ERROR: `%s` does not have FluentExtension installed. Continuing without it...', $dataClass) . PHP_EOL;
+                    unset($classesWithFluent[$key]);
+                }
+            }
+        } else {
+            $dataClasses = static::getDirectSubclassesRecursivelyFor(DataObject::class);
+            $classesWithFluent = $this->filterPermittedClassesRecursively($dataClasses);
+        }
+
+        foreach ($classesWithFluent as $classWithFluent) {
+            if (!$this->isClassNamePermitted($classWithFluent)) {
+                continue;
+            }
+
+            $results = $this->doLocaliseClass($classWithFluent, $globalLocale, $limit, $publish);
+            foreach ($results as $key => $value) {
+                $total_results[$key] += $value;
+            }
+
+            echo sprintf('Processing %s objects...', $classWithFluent) . PHP_EOL;
+            echo sprintf('└─ Localised %d of %d objects.', $results['localised'], $results['localisable']) . PHP_EOL;
+            if ($results['publishable']) {
+                echo sprintf('└─ Published %d of %d objects.', $results['published'], $results['publishable']) . PHP_EOL;
+            }
+        }
+
+        echo PHP_EOL;
+        echo sprintf('Completed %d classes.', count($classesWithFluent)) . PHP_EOL;
+        echo sprintf('└─ Localised %d of %d objects in total.', $total_results['localised'], $total_results['localisable']) . PHP_EOL;
+        echo PHP_EOL;
+
+        if ($total_results['publishable']) {
+            echo sprintf('└─ Published %d of %d objects in total.', $total_results['published'], $total_results['publishable']) . PHP_EOL;
+            echo PHP_EOL;
+        }
+
+        if (!Director::is_cli()) {
+            echo '
'; + } + } + + /** + * @param $className + * @param $globalLocale + * @param $limit + * @param $publish + * @return array{localisable: int, localised: int, publishable: int, published: int} + * @throws \SilverStripe\ORM\ValidationException + */ + protected function doLocaliseClass($className, $globalLocale, $limit, $publish): array + { + $dataObjectIDs = FluentState::singleton()->withState(static function (FluentState $state) use ($className, $limit): array { + $state->setLocale(null); + $dataObjects = $className::get()->sort('ID', 'ASC'); + + if ($limit > 0) { + $dataObjects = $dataObjects->limit($limit); + } + + return $dataObjects->column('ID'); + }); + + return FluentState::singleton()->withState( + static function (FluentState $state) use ($className, $globalLocale, $publish, $dataObjectIDs): array { + $state->setLocale($globalLocale->Locale); + $return = [ + 'localisable' => 0, + 'localised' => 0, + 'publishable' => 0, + 'published' => 0, + ]; + + foreach ($dataObjectIDs as $dataObjectID) { + /** @var DataObject|FluentExtension $dataObject */ + $dataObject = $className::get()->byID($dataObjectID); + $return['localisable'] += 1; + + if (!$dataObject->hasExtension(FluentVersionedExtension::class)) { + if ($dataObject->existsInLocale()) { + continue; + } + $dataObject->write(); + $return['localised'] += 1; + continue; + } + + // We have versioned data, so start tracking how many have been published + $return['publishable'] += 1; + + /** @var DataObject|Versioned|FluentVersionedExtension $dataObject */ + if ($dataObject->isDraftedInLocale()) { + continue; + } + $dataObject->writeToStage(Versioned::DRAFT); + + $return['localised'] += 1; + + if (!$publish) { + continue; + } + + // Check if the base record was published - if not then we don't need to publish + // as this would leak draft content, we only want to publish pages which were published + // before Fluent module was added + $dataObjectID = $dataObject->ID; + $isBaseRecordPublished = FluentState::singleton()->withState( + static function (FluentState $state) use ($className, $dataObjectID): bool { + $state->setLocale(null); + $page = $className::get_by_id($dataObjectID); + + if ($page === null) { + return false; + } + + return $page->isPublished(); + } + ); + + if (!$isBaseRecordPublished) { + continue; + } + + $dataObject->publishRecursive(); + $return['published'] += 1; + } + + return $return; + } + ); + } + + /** + * @param string $className + * @return array[] + * @throws \ReflectionException + */ + protected static function getDirectSubclassesRecursivelyFor(string $className): array + { + $directSubclasses = []; + foreach (ClassInfo::subclassesFor($className, false) as $subclassName) { + $actualParentClass = get_parent_class($subclassName); + if ($className === $actualParentClass) { + $directSubclasses[$subclassName] = static::getDirectSubclassesRecursivelyFor($subclassName); + } + } + + return $directSubclasses; + } + + /** + * @param array $classes + * @return array + */ + protected function filterPermittedClassesRecursively(array $classes): array + { + $permittedClasses = []; + foreach ($classes as $parentClassName => $subclassNames) { + if ($this->isClassNamePermitted($parentClassName)) { + $permittedClasses[] = $parentClassName; + // We will skip all subclasses since the ORM will automatically + // pull them in when this parent is referenced + continue; + } + + $permittedClasses = array_merge($permittedClasses, $this->filterPermittedClassesRecursively($subclassNames)); + } + + return $permittedClasses; + } + + /** + * @param string $className + * @return bool + */ + protected function isClassNamePermitted(string $className): bool + { + // Do a simple (inexpensive) text comparison against the exclusion list before we create an object + if (!$this->include_only_classes && is_array($this->exclude_classes) && in_array($className, $this->exclude_classes)) { + return false; + } + + /** @var DataObject $dataObject */ + $dataObject = singleton($className); + + // Now we'll do a full comparison against the exclusion list + // This important step will, for example, match (refuse) a BlogPost if Page is listed as excluded + if (is_array($this->exclude_classes)) { + foreach ($this->exclude_classes as $excluded_class) { + if ($dataObject instanceof $excluded_class) { + return false; + } + } + } + + return $dataObject->hasExtension(FluentExtension::class); + } +} diff --git a/src/Task/InitialPageLocalisationTask.php b/src/Task/InitialPageLocalisationTask.php index 07724630..d39bd7bc 100644 --- a/src/Task/InitialPageLocalisationTask.php +++ b/src/Task/InitialPageLocalisationTask.php @@ -3,14 +3,8 @@ namespace TractorCow\Fluent\Task; use SilverStripe\CMS\Model\SiteTree; -use SilverStripe\Control\HTTPRequest; -use SilverStripe\Dev\BuildTask; -use SilverStripe\Versioned\Versioned; -use TractorCow\Fluent\Extension\FluentSiteTreeExtension; -use TractorCow\Fluent\Model\Locale; -use TractorCow\Fluent\State\FluentState; -class InitialPageLocalisationTask extends BuildTask +class InitialPageLocalisationTask extends InitialDataObjectLocalisationTask { /** * @var string @@ -20,94 +14,34 @@ class InitialPageLocalisationTask extends BuildTask /** * @var string */ - protected $title = 'Initial page localisation'; + protected $title = 'Initial SiteTree localisation'; /** * @var string */ - protected $description = 'Intended for projects which already have some pages when Fluent module is added.' . - ' This dev task will localise / publish all pages in the default locale. Locale setup has to be done before running this task.' . - ' Pages which are not published will not be published, only localised. Pages which are already localised will be skipped.'; + protected $description = 'Intended for projects which already have some Pages when Fluent module is added.' . + ' This dev task will localise / publish all Pages in the default locale. Locale setup has to be done before running this task.' . + ' Pass limit=N to limit number of records to localise. Pass publish=1 to force publishing of localised Pages.' . + ' Regardless, Pages which were not already published will not be published, only localised. Pages which were already localised will always be skipped.'; /** - * @param HTTPRequest $request + * @var string[] */ - public function run($request) - { - $publish = (bool) $request->getVar('publish'); - $limit = (int) $request->getVar('limit'); - - /** @var Locale $globalLocale */ - $globalLocale = Locale::get() - ->filter(['IsGlobalDefault' => 1]) - ->sort('ID', 'ASC') - ->first(); - - if (!$globalLocale) { - echo 'Please set global locale first!' . PHP_EOL; - - return; - } - - $pageIds = FluentState::singleton()->withState(static function (FluentState $state) use ($limit): array { - $state->setLocale(null); - $pages = SiteTree::get()->sort('ID', 'ASC'); - - if ($limit > 0) { - $pages = $pages->limit($limit); - } - - return $pages->column('ID'); - }); - - $localised = FluentState::singleton()->withState( - static function (FluentState $state) use ($globalLocale, $pageIds, $publish): int { - $state->setLocale($globalLocale->Locale); - $localised = 0; - - foreach ($pageIds as $pageId) { - /** @var SiteTree|FluentSiteTreeExtension $page */ - $page = SiteTree::get()->byID($pageId); - - if ($page->isDraftedInLocale()) { - continue; - } - - $page->writeToStage(Versioned::DRAFT); - $localised += 1; + protected $include_only_classes = [ + SiteTree::class + ]; - if (!$publish) { - continue; - } - - // Check if the base record was published - if not then we don't need to publish - // as this would leak draft content, we only want to publish pages which were published - // before Fluent module was added - $pageId = $page->ID; - $isBaseRecordPublished = FluentState::singleton()->withState( - static function (FluentState $state) use ($pageId): bool { - $state->setLocale(null); - $page = SiteTree::get_by_id($pageId); - - if ($page === null) { - return false; - } - - return $page->isPublished(); - } - ); - - if (!$isBaseRecordPublished) { - continue; - } - - $page->publishRecursive(); - } - - return $localised; - } - ); + /** + * @var string[] + */ + protected $exclude_classes = []; - echo sprintf('Localised %d pages.', $localised) . PHP_EOL; + /** + * Soft dependency on CMS module + * @return bool + */ + function isEnabled(): bool + { + return class_exists(SiteTree::class) && parent::isEnabled(); } } From 07328cf9f0dde51107916a4b4a3b5b922c4b48c5 Mon Sep 17 00:00:00 2001 From: "Nathan J. Brauer" Date: Tue, 2 Apr 2024 13:21:37 -0700 Subject: [PATCH 16/16] DOC Updated documentation for new DataObject initialisation task --- docs/en/migrating-from-single-language.md | 147 +++++++++++++++++----- 1 file changed, 119 insertions(+), 28 deletions(-) diff --git a/docs/en/migrating-from-single-language.md b/docs/en/migrating-from-single-language.md index 6fce063c..5137e43d 100644 --- a/docs/en/migrating-from-single-language.md +++ b/docs/en/migrating-from-single-language.md @@ -4,12 +4,14 @@ In case you want to add fluent to an existing site to add multi language functio ## Install fluent -use composer to install fluent, see [installation](installation.md) +Use composer to install fluent, see [installation](installation.md) ## Configure fluent -* add locales -You can either do this in the backend, or for the first setup you can utitlise `default_records` to add the locales to the db. +* Add locales + +You can either do this in the backend, or for the first setup you can utitlise `default_records` to add the locales to +the db. A fluent.yml might look like: ``` @@ -33,32 +35,121 @@ TractorCow\Fluent\Model\Locale: When you run `dev/build?flush` again, this adds the records to the database if the locales table is still empty. -## Publish available pages in your default locale +## Populating initial localised content for existing Pages and DataObjects in your default locale -Now your site is broken, cause no pages have been published and added as translated page in your default locale. -You can either publish all pages manually or use [publishall](https://docs.silverstripe.org/en/4/developer_guides/debugging/url_variable_tools/#building-and-publishing-urls) to publish all pages in bulk. -If you run `/admin/pages/publishall` in your browser your site will be fixed again and you can start adding translated content. +Now your site is broken because nothing has been published and added as translated data in your default locale. You can +either manually localise all DataObjects & Pages manually or use one of the automation options below. ### Automated tools for localisation -`InitialPageLocalisation` dev task can be used to either only localise or localise & publish your pages. -This dev task can be run either via CLI or queued as a job if Queued jobs module is installed. - -Localise only example - -``` -dev/tasks/initial-page-localisation-task -``` - -Localise & publish example - -``` -dev/tasks/initial-page-localisation-task publish=1 -``` - -Localisation in batches can be done by using the `limit` option. -Example below will localise & publish five pages on each run. - -``` -dev/tasks/initial-page-localisation-task publish=1&limit=5 -``` +#### From the CMS (SiteTree only) + +Use Silverstripe's +built-in [publishall](https://docs.silverstripe.org/en/4/developer_guides/debugging/url_variable_tools/#building-and-publishing-urls) +tool to publish all Pages in bulk. +Run `/admin/pages/publishall` in your browser and your site will be fixed again and you can start adding translated +content. + +_This method will work with Pages only (not localised DataObjects)._ + +#### Commandline or Queued Jobs (SiteTree and DataObjects) + +The `InitialPageLocalisation` and `InitialDataObjectLocalisationTask` dev tasks may be used to localise and, optionally, +publish your `Versioned` data (including Pages) from the commandline or queued as a job (if the Queued Jobs module is installed). + +`InitialPageLocalisation` - localise all `SiteTree` objects (Pages) + +`InitialDataObjectLocalisationTask` - localise all Fluent-enabled DataObjects (excluding `SiteTree`) + +1. Example: Localise all Pages (default, without publishing) + + ``` + dev/tasks/initial-page-localisation-task + ``` + +2. Example: Localise & publish all Pages + + ``` + dev/tasks/initial-page-localisation-task publish=1 + ``` + +3. Example: Localising Pages in batches can be done by using the `limit` option. + This will localise & publish five pages on each run. + + ``` + dev/tasks/initial-page-localisation-task publish=1&limit=5 + ``` + +4. Example: All the same functionality is available for localising all DataObjects, including `Versioned` and non-Versioned classes + + ``` + dev/tasks/initial-dataobject-localisation-task + ``` + or + + ``` + dev/tasks/initial-dataobject-localisation-task publish=1&limit=5 + ``` + +#### Customize your own initialisation dev task + +Perhaps you want to be more selective in how you initialise your localised content. +The `InitialDataObjectLocalisationTask` class can be easily extended to either list exactly which classes you want to +initially localise, or you can exclude specific classes from initialisation. + +1. **Initialise specific classes:** The following example will create a task which localises **_ONLY_** `BlogPost` +pages, `Testimonial` objects, _and their subclasses (if any)_. + + ```php + class CustomLocalisationTask extends InitialDataObjectLocalisationTask + { + /** + * @var string + */ + private static $segment = 'custom-localisation-initialisation-task'; + + /** + * @var string + */ + protected $title = 'Custom localisation initialisation'; + + /** + * @var string[] + */ + protected array $include_only_classes = [ + \SilverStripe\Blog\Model\BlogPost::class, + \AcmeCo\Model\Testimonial::class + ]; + + } + ``` + +2. **Initialise all DataObjects but exclude some:** The following example will create a task which localises **_ALL_** +DataObjects **_except_** `BlogPost` pages, `Testimonial` objects, _and their subclasses (if any)_. + + ```php + class CustomLocalisationTask extends InitialDataObjectLocalisationTask + { + /** + * @var string + */ + private static $segment = 'custom-localisation-initialisation-task'; + + /** + * @var string + */ + protected $title = 'Custom localisation initialisation'; + + /** + * @var string[] + */ + protected array $exclude_classes = [ + \SilverStripe\Blog\Model\BlogPost::class, + \AcmeCo\Model\Testimonial::class + ]; + + } + ``` + +3. **One or the other:** You may specify `$include_only_classes` OR `$exclude_classes` - not both. +If `$include_only_classes` is not an empty array, `$exclude_classes` will be ignored.