diff --git a/docs/en/installation.md b/docs/en/installation.md index 1fbb4e83..29a2c5e2 100644 --- a/docs/en/installation.md +++ b/docs/en/installation.md @@ -7,7 +7,7 @@ with composer. composer require tractorcow/silverstripe-fluent ^5 ``` - * Run a `dev/build` to ensure all additional table fields have been generated + * Run `sake db:build --flush` to ensure all additional table fields have been generated * Configure your locales in the `/admin/locales` section * Publish pages in each of the locales you want them to be visible in diff --git a/docs/en/migrating-from-single-language.md b/docs/en/migrating-from-single-language.md index 5137e43d..f5d5f723 100644 --- a/docs/en/migrating-from-single-language.md +++ b/docs/en/migrating-from-single-language.md @@ -33,7 +33,7 @@ TractorCow\Fluent\Model\Locale: URLSegment: en ``` -When you run `dev/build?flush` again, this adds the records to the database if the locales table is still empty. +When you run `sake db:build --flush` again, this adds the records to the database if the locales table is still empty. ## Populating initial localised content for existing Pages and DataObjects in your default locale @@ -63,32 +63,33 @@ publish your `Versioned` data (including Pages) from the commandline or queued a 1. Example: Localise all Pages (default, without publishing) - ``` - dev/tasks/initial-page-localisation-task + ```sh + sake tasks:initial-page-localisation-task ``` 2. Example: Localise & publish all Pages - ``` - dev/tasks/initial-page-localisation-task publish=1 + ```sh + sake tasks:initial-page-localisation-task --publish ``` 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 + ```sh + sake tasks:initial-page-localisation-task --publish --limit=5 ``` 4. Example: All the same functionality is available for localising all DataObjects, including `Versioned` and non-Versioned classes + ```sh + sake tasks:initial-dataobject-localisation-task ``` - dev/tasks/initial-dataobject-localisation-task - ``` + or - ``` - dev/tasks/initial-dataobject-localisation-task publish=1&limit=5 + ```sh + sake tasks:initial-dataobject-localisation-task --publish --limit=5 ``` #### Customize your own initialisation dev task diff --git a/src/Task/ConvertTranslatableTask.php b/src/Task/ConvertTranslatableTask.php deleted file mode 100644 index 587fac7b..00000000 --- a/src/Task/ConvertTranslatableTask.php +++ /dev/null @@ -1,222 +0,0 @@ - Fluent Task"; - - protected $description = "Migrates site DB from SS3 Translatable DB format to SS4 Fluent."; - - private static $segment = 'ConvertTranslatableTask'; - - public function __construct() - { - parent::__construct(); - Deprecation::withSuppressedWarning(function () { - Deprecation::notice( - '7.3.0', - 'Will be removed without equivalent functionality to replace it', - Deprecation::SCOPE_CLASS - ); - }); - } - - /** - * Checks that fluent is configured correctly - * - * @throws ConvertTranslatableTask\Exception - */ - protected function checkInstalled() - { - // Assert that fluent is configured - $locales = Locale::getLocales(); - if (empty($locales)) { - throw new Exception("Please configure Fluent locales (in the CMS) prior to migrating from translatable"); - } - - $defaultLocale = Locale::getDefault(); - if (empty($defaultLocale)) { - throw new Exception( - "Please configure a Fluent default locale (in the CMS) prior to migrating from translatable" - ); - } - } - - /** - * Gets all classes with FluentExtension - * - * @return array Array of classes to migrate - */ - public function fluentClasses() - { - $classes = []; - $dataClasses = ClassInfo::subclassesFor(DataObject::class); - array_shift($dataClasses); - foreach ($dataClasses as $class) { - $base = DataObject::getSchema()->baseDataClass($class); - foreach (DataObject::get_extensions($base) as $extension) { - if (is_a($extension, FluentExtension::class, true)) { - $classes[] = $base; - break; - } - } - } - return array_unique($classes); - } - - public function run($request) - { - $this->checkInstalled(); - - // we may need some privileges for this to work - // without this, running under sake is a problem - // maybe sake could take care of it ... - Member::actAs( - DefaultAdminService::singleton()->findOrCreateDefaultAdmin(), - function () { - DB::get_conn()->withTransaction(function () { - Versioned::set_stage(Versioned::DRAFT); - $classes = $this->fluentClasses(); - $tables = DB::get_schema()->tableList(); - if (empty($classes)) { - Debug::message('No classes have Fluent enabled, so skipping.', false); - } - - foreach ($classes as $class) { - /** @var DataObject $class */ - - // Ensure that a translationgroup table exists for this class - $baseTable = DataObject::getSchema()->baseDataTable($class); - $groupTable = strtolower($baseTable . "_translationgroups"); - if (isset($tables[$groupTable])) { - $groupTable = $tables[$groupTable]; - } else { - Debug::message("Ignoring class without _translationgroups table $class", false); - continue; - } - - // Disable filter if it has been applied to the class - if (singleton($class)->hasMethod('has_extension') - && $class::has_extension(FluentFilteredExtension::class) - ) { - $class::remove_extension(FluentFilteredExtension::class); - } - - // Select all instances of this class in the base table, where the Locale field is not null. - // Translatable has a Locale column on the base table in SS3, but Fluent doesn't use it. Newly - // created records via SS4 Fluent will not set this column, but will set it in {$baseTable}_Localised - $instances = DataObject::get($class, sprintf( - '"%s"."Locale" IS NOT NULL', - $baseTable - )); - - foreach ($instances as $instance) { - // Get the Locale column directly from the base table, since the SS ORM will not include it - $instanceLocale = SQLSelect::create() - ->setFrom("\"{$baseTable}\"") - ->setSelect('"Locale"') - ->setWhere(["\"{$baseTable}\".\"ID\"" => $instance->ID]) - ->execute() - ->first(); - - // Ensure that we got the Locale out of the base table before continuing - if (empty($instanceLocale['Locale'])) { - Debug::message("Skipping {$instance->Title} with ID {$instance->ID} - couldn't find Locale"); - continue; - } - $instanceLocale = $instanceLocale['Locale']; - - // Check for obsolete classes that don't need to be handled any more - if ($instance->ObsoleteClassName) { - Debug::message( - "Skipping {$instance->ClassName} with ID {$instance->ID} because it from an obsolete class", - false - ); - continue; - } - - Debug::message( - "Updating {$instance->ClassName} {$instance->Title} ({$instance->ID}) with locale {$instanceLocale}", - false - ); - - FluentState::singleton() - ->withState(function (FluentState $state) use ($instance, $instanceLocale) { - // Use Fluent's ORM to write and/or publish the record into the correct locale - // from Translatable - $state->setLocale($instanceLocale); - - if (!$this->isPublished($instance)) { - $instance->write(); - Debug::message(" -- Saved to draft", false); - } elseif ($instance->publishRecursive() === false) { - Debug::message(" -- Publishing FAILED", false); - throw new Exception("Failed to publish"); - } else { - Debug::message(" -- Published", false); - } - }); - } - - // Drop the "Locale" column from the base table - Debug::message('Dropping "Locale" column from ' . $baseTable, false); - DB::query(sprintf('ALTER TABLE "%s" DROP COLUMN "Locale"', $baseTable)); - - // Drop the "_translationgroups" translatable table - Debug::message('Deleting Translatable table ' . $groupTable, false); - DB::query(sprintf('DROP TABLE IF EXISTS "%s"', $groupTable)); - } - }); - } - ); - } - - /** - * Determine whether the record has been published previously/is currently published - * - * @param DataObject $instance - * @return bool - */ - protected function isPublished(DataObject $instance) - { - $isPublished = false; - if ($instance->hasMethod('isPublished')) { - $isPublished = $instance->isPublished(); - } - return $isPublished; - } -} diff --git a/src/Task/ConvertTranslatableTask/Exception.php b/src/Task/ConvertTranslatableTask/Exception.php deleted file mode 100644 index 3d0cc2d3..00000000 --- a/src/Task/ConvertTranslatableTask/Exception.php +++ /dev/null @@ -1,23 +0,0 @@ -' . PHP_EOL; - } + $output->writeForHtml('
'); - $publish = (bool)$request->getVar('publish'); - $limit = (int)$request->getVar('limit'); + $publish = $input->getOption('publish'); + $limit = (int)$input->getOption('limit'); $total_results = [ 'localisable' => 0, @@ -79,16 +66,16 @@ public function run($request) ->first(); if (!$globalLocale) { - echo 'Please set global locale first!' . PHP_EOL; - - return; + $output->writeln(''); + return Command::INVALID; } 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; + $output->writeln(sprintf('ERROR: `%s` does not have FluentExtension installed. Continuing without it...', $dataClass)); unset($classesWithFluent[$key]); } } @@ -107,26 +94,33 @@ public function run($request) $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; + $output->writeln(sprintf('Processing %s objects...', $classWithFluent)); + $output->writeln(sprintf('└─ Localised %d of %d objects.', $results['localised'], $results['localisable'])); if ($results['publishable']) { - echo sprintf('└─ Published %d of %d objects.', $results['published'], $results['publishable']) . PHP_EOL; + $output->writeln(sprintf('└─ Published %d of %d objects.', $results['published'], $results['publishable'])); } } - 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; + $output->writeln(''); + $output->writeln(sprintf('Completed %d classes.', count($classesWithFluent))); + $output->writeln(sprintf('└─ Localised %d of %d objects in total.', $total_results['localised'], $total_results['localisable'])); + $output->writeln(''); if ($total_results['publishable']) { - echo sprintf('└─ Published %d of %d objects in total.', $total_results['published'], $total_results['publishable']) . PHP_EOL; - echo PHP_EOL; + $output->writeln(sprintf('└─ Published %d of %d objects in total.', $total_results['published'], $total_results['publishable'])); + $output->writeln(''); } - if (!Director::is_cli()) { - echo ''; - } + $output->writeForHtml('Please set global locale first!>'); + $output->writeForHtml('
'); + return Command::SUCCESS; + } + + public function getOptions(): array + { + return [ + new InputOption('publish', null, InputOption::VALUE_NONE, 'Publish pages after localising (if they were published beforehand)'), + new InputOption('limit', null, InputOption::VALUE_REQUIRED, 'Maximum number of records to localise at once', 1), + ]; } /** @@ -284,4 +278,17 @@ protected function isClassNamePermitted(string $className): bool return $dataObject->hasExtension(FluentExtension::class); } + + public static function getHelp(): string + { + $isCli = Director::is_cli(); + $limit = $isCli ? '--limit=N' : 'limit=N'; + $publish = $isCli ? '--publish' : 'publish=1'; + return <<$limit> to limit number of records to localise. Pass $publish> to enable publishing of localised Versioned DataObjects. + Regardless, Versioned DataObjects which were not already published will not be published, only localised. DataObjects which were already localised will always be skipped. + This class may be extended to create custom initialization tasks targeting or excluding specific classes. + TXT; + } } diff --git a/src/Task/InitialPageLocalisationTask.php b/src/Task/InitialPageLocalisationTask.php index d39bd7bc..24ca9668 100644 --- a/src/Task/InitialPageLocalisationTask.php +++ b/src/Task/InitialPageLocalisationTask.php @@ -3,26 +3,15 @@ namespace TractorCow\Fluent\Task; use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\Control\Director; class InitialPageLocalisationTask extends InitialDataObjectLocalisationTask { - /** - * @var string - */ - private static $segment = 'initial-page-localisation-task'; + protected static string $commandName = 'initial-page-localisation-task'; - /** - * @var string - */ - protected $title = 'Initial SiteTree localisation'; + protected string $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.' . - ' 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.'; + protected static string $description = 'Intended for projects which already have some Pages when Fluent module is added'; /** * @var string[] @@ -44,4 +33,16 @@ function isEnabled(): bool { return class_exists(SiteTree::class) && parent::isEnabled(); } + + public static function getHelp(): string + { + $isCli = Director::is_cli(); + $limit = $isCli ? '--limit=N' : 'limit=N'; + $publish = $isCli ? '--publish' : 'publish=1'; + return << $limit> to limit number of records to localise. Pass $publish> 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. + TXT; + } } diff --git a/tests/php/Task/InitialPageLocalisationTaskTest.php b/tests/php/Task/InitialPageLocalisationTaskTest.php index bdb121f4..b54a01d9 100644 --- a/tests/php/Task/InitialPageLocalisationTaskTest.php +++ b/tests/php/Task/InitialPageLocalisationTaskTest.php @@ -3,11 +3,11 @@ namespace TractorCow\Fluent\Tests\Task; use SilverStripe\CMS\Model\SiteTree; -use SilverStripe\Control\HTTPRequest; use SilverStripe\Dev\SapphireTest; -use SilverStripe\Core\Validation\ValidationException; +use SilverStripe\PolyExecution\PolyOutput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; use TractorCow\Fluent\Extension\FluentSiteTreeExtension; -use TractorCow\Fluent\Model\Locale; use TractorCow\Fluent\State\FluentState; use TractorCow\Fluent\Task\InitialPageLocalisationTask; use PHPUnit\Framework\Attributes\DataProvider; @@ -75,7 +75,12 @@ public function testInitialPageLocalisation(bool $publish, int $limit, array $lo ]; // Localise pages - InitialPageLocalisationTask::singleton()->run(new HTTPRequest('GET', '/', $getParams)); + $task = InitialPageLocalisationTask::singleton(); + $buffer = new BufferedOutput(); + $output = new PolyOutput(PolyOutput::FORMAT_ANSI, wrappedOutput: $buffer); + $input = new ArrayInput($getParams); + $input->setInteractive(false); + $task->run($input, $output); // Check localised records (should have all pages now) $pages = $this->getLocalisedPages();