diff --git a/src/BatchPublish/Asset/Job.php b/src/BatchPublish/Asset/Job.php new file mode 100644 index 00000000..3b6fd33e --- /dev/null +++ b/src/BatchPublish/Asset/Job.php @@ -0,0 +1,47 @@ +items = $items; + } + + /** + * @param mixed $item + */ + protected function processItem($item): void + { + Versioned::withVersionedMode(function () use ($item): void { + Versioned::set_stage(Versioned::DRAFT); + + /** @var File $file */ + $file = File::get()->byID($item); + + if (!$file || !$file->exists()) { + $this->addMessage('File not found ' . $item); + + return; + } + + $file->write(); + + // force new version to be written + $file->copyVersionToStage(Versioned::DRAFT, Versioned::DRAFT); + + $file->publishRecursive(); + }); + } +} diff --git a/src/BatchPublish/Asset/Task.php b/src/BatchPublish/Asset/Task.php new file mode 100644 index 00000000..2aa32d23 --- /dev/null +++ b/src/BatchPublish/Asset/Task.php @@ -0,0 +1,37 @@ +sort('ID', 'ASC'); + $this->queueJobsFromList($request, $list, Job::class, self::CHUNK_SIZE); + }); + } +} diff --git a/src/BatchPublish/Folder/Job.php b/src/BatchPublish/Folder/Job.php new file mode 100644 index 00000000..5f799810 --- /dev/null +++ b/src/BatchPublish/Folder/Job.php @@ -0,0 +1,51 @@ +items = $items; + } + + /** + * @param mixed $item + */ + protected function processItem($item): void + { + Versioned::withVersionedMode(function () use ($item): void { + Versioned::set_stage(Versioned::DRAFT); + + /** @var Folder $folder */ + $folder = Folder::get()->byID($item); + + if (!$folder) { + $this->addMessage('Folder not found ' . $item); + + return; + } + + if ($folder->isPublished()) { + return; + } + + $folder->write(); + + // force new version to be written + $folder->copyVersionToStage(Versioned::DRAFT, Versioned::DRAFT); + + $folder->publishRecursive(); + }); + } +} diff --git a/src/BatchPublish/Folder/Task.php b/src/BatchPublish/Folder/Task.php new file mode 100644 index 00000000..dd8bac43 --- /dev/null +++ b/src/BatchPublish/Folder/Task.php @@ -0,0 +1,39 @@ +sort('ID', 'ASC'); + + $this->queueJobsFromList($request, $list, Job::class, self::CHUNK_SIZE); + }); + } +} diff --git a/src/BatchPublish/Page/Job.php b/src/BatchPublish/Page/Job.php new file mode 100644 index 00000000..7c158e0a --- /dev/null +++ b/src/BatchPublish/Page/Job.php @@ -0,0 +1,50 @@ +items = $items; + } + + /** + * @param mixed $item + */ + protected function processItem($item): void + { + Versioned::withVersionedMode(function () use ($item): void { + Versioned::set_stage(Versioned::DRAFT); + + /** @var SiteTree $page */ + $page = SiteTree::get()->byID($item); + + if ($page === null) { + $this->addMessage('Page not found ' . $item); + + return; + } + + Page::singleton()->withSkippedSiblingSortPublish(static function () use ($page): void { + $page->write(); + + // force new version to be written + $page->copyVersionToStage(Versioned::DRAFT, Versioned::DRAFT); + + $page->publishRecursive(); + }); + }); + } +} diff --git a/src/BatchPublish/Page/Task.php b/src/BatchPublish/Page/Task.php new file mode 100644 index 00000000..6a7cc5f7 --- /dev/null +++ b/src/BatchPublish/Page/Task.php @@ -0,0 +1,37 @@ +sort('ID', 'ASC'); + $this->queueJobsFromList($request, $list, Job::class, self::CHUNK_SIZE); + }); + } +} diff --git a/src/FileMigration/FileBinary/Helper.php b/src/FileMigration/FileBinary/Helper.php new file mode 100644 index 00000000..3324ac21 --- /dev/null +++ b/src/FileMigration/FileBinary/Helper.php @@ -0,0 +1,60 @@ +ids = $ids; + + return $this; + } + + public function getFileQuery(): DataList + { + // public scope change + return parent::getFileQuery(); + } + + /** + * Code taken from @see FileMigrationHelper::chunk() + * + * @param DataList $query + * @return Generator + */ + protected function chunk(DataList $query): Generator + { + // only select specified files + $query = $query->byIDs($this->ids); + + // the rest of the code is just a copy from base + $chunkSize = 100; + $greaterThanID = 0; + $query = $query->limit($chunkSize)->sort('ID'); + + while ($chunk = $query->filter('ID:GreaterThan', $greaterThanID)) { + /** @var File $file */ + foreach ($chunk as $file) { + yield $file; + } + + if ($chunk->count() === 0) { + break; + } + + $greaterThanID = $file->ID; + } + } +} diff --git a/src/FileMigration/FileBinary/Job.php b/src/FileMigration/FileBinary/Job.php new file mode 100644 index 00000000..ed45ed34 --- /dev/null +++ b/src/FileMigration/FileBinary/Job.php @@ -0,0 +1,73 @@ +items = $items; + } + + /** + * @param mixed $item + */ + protected function processItem($item): void + { + $this->withExecutionTime(self::TIME_LIMIT, function () use ($item): void { + $logger = new Queue\Logger(); + $logger->setJob($this); + + $result = Helper::create() + ->setIds([$item]) + ->setLogger($logger) + ->run(); + + if ($result) { + return; + } + + $message = sprintf('File migration failed for file %d', $item); + + if (count($this->items) > 1 || !$this->checkFileBinary($item)) { + // suppress exception in case we are migrating a batch or file binary is missing + // missing file binaries are not a problem of this migration process + // so we don't need to log them as errors + $this->addMessage($message); + + return; + } + + throw new RuntimeException($message); + }); + } + + private function checkFileBinary(int $id): bool + { + $query = SQLSelect::create('"Filename"', '"File"', ['"ID"' => $id]); + $results = $query->execute(); + $result = $results->first(); + + if (!$result) { + return false; + } + + $filename = $result['Filename']; + + return file_exists($filename); + } +} diff --git a/src/FileMigration/FileBinary/Task.php b/src/FileMigration/FileBinary/Task.php new file mode 100644 index 00000000..caf2460e --- /dev/null +++ b/src/FileMigration/FileBinary/Task.php @@ -0,0 +1,36 @@ +getFileQuery() + ->sort('ID', 'ASC'); + + $this->queueJobsFromList($request, $list, Job::class, self::CHUNK_SIZE); + } +} diff --git a/src/FileMigration/FixFolderPermission/Job.php b/src/FileMigration/FixFolderPermission/Job.php new file mode 100644 index 00000000..7adc8e92 --- /dev/null +++ b/src/FileMigration/FixFolderPermission/Job.php @@ -0,0 +1,45 @@ +totalSteps = 1; + } + + public function process(): void + { + $logger = new Queue\Logger(); + $logger->setJob($this); + + $count = FixFolderPermissionsHelper::singleton() + ->setLogger($logger) + ->run(); + + $message = $count > 0 + ? sprintf('Repaired %s folders with broken CanViewType settings', $count) + : 'No folders required fixes'; + + $this->addMessage($message); + $this->currentStep += 1; + $this->isComplete = true; + } +} diff --git a/src/FileMigration/FixFolderPermission/Task.php b/src/FileMigration/FixFolderPermission/Task.php new file mode 100644 index 00000000..bc637cc4 --- /dev/null +++ b/src/FileMigration/FixFolderPermission/Task.php @@ -0,0 +1,33 @@ +queueJob($job); + } +} diff --git a/src/FileMigration/ImageThumbnail/Helper.php b/src/FileMigration/ImageThumbnail/Helper.php new file mode 100644 index 00000000..32a32f2d --- /dev/null +++ b/src/FileMigration/ImageThumbnail/Helper.php @@ -0,0 +1,16 @@ +items = $items; + } + + public function setup(): void + { + $this->remaining = $this->items; + $this->totalSteps = count($this->items); + } + + /** + * Code taken from @see ImageThumbnailHelper::run() + * + * @param mixed $item + */ + protected function processItem($item): void + { + $this->withExecutionTime(self::TIME_LIMIT, function () use ($item): void { + /** @var File $file */ + $file = File::get()->byID($item); + + // Skip if file is not an image + if (!$file->getIsImage()) { + $this->addMessage(printf('File is not an image: %s', $file->Filename)); + + return; + } + + $logger = new Queue\Logger(); + $logger->setJob($this); + + $helper = Helper::create() + ->setLogger($logger); + + $generated = $helper->generateThumbnails($file); + + if (count($generated) === 0) { + return; + } + + $this->addMessage(sprintf('Generated thumbnail for %s', $file->Filename)); + }); + } +} diff --git a/src/FileMigration/ImageThumbnail/Task.php b/src/FileMigration/ImageThumbnail/Task.php new file mode 100644 index 00000000..dc148509 --- /dev/null +++ b/src/FileMigration/ImageThumbnail/Task.php @@ -0,0 +1,34 @@ +sort('ID', 'ASC'); + $this->queueJobsFromList($request, $list, Job::class, self::CHUNK_SIZE); + } +} diff --git a/src/FileMigration/IntegrityCheck/Task.php b/src/FileMigration/IntegrityCheck/Task.php new file mode 100644 index 00000000..9cf70603 --- /dev/null +++ b/src/FileMigration/IntegrityCheck/Task.php @@ -0,0 +1,89 @@ +postVar('assets'); + + if ($file && is_array($file) && array_key_exists('tmp_name', $file)) { + $lines = file_get_contents($file['tmp_name']); + $lines = explode(PHP_EOL, $lines); + + if (count($lines) === 0) { + return; + } + + $deleted = []; + $files = File::get() + ->sort('ID', 'ASC') + ->map('ID', 'ID') + ->toArray(); + + foreach ($lines as $line) { + $line = explode(';', $line); + $id = array_shift($line); + $path = array_shift($line); + + if (array_key_exists($id, $files)) { + continue; + } + + $deleted[$id] = $path; + } + + if (count($deleted) === 0) { + return; + } + + $lines = []; + + foreach ($deleted as $id => $path) { + $lines[] = $id . ';' . $path; + } + + $data = implode(PHP_EOL, $lines); + $outputPath = BASE_PATH . DIRECTORY_SEPARATOR . 'deleted-assets.csv'; + file_put_contents($outputPath, $data); + + echo 'Done'; + + return; + } + + echo '
'; + }); + } +} diff --git a/src/FileMigration/LegacyThumbnail/Helper.php b/src/FileMigration/LegacyThumbnail/Helper.php new file mode 100644 index 00000000..f5595a32 --- /dev/null +++ b/src/FileMigration/LegacyThumbnail/Helper.php @@ -0,0 +1,24 @@ +items = $items; + } + + /** + * Code taken from @see LegacyThumbnailMigrationHelper::run() + * + * @param mixed $item + */ + protected function processItem($item): void + { + // Set max time and memory limit + Environment::increaseTimeLimitTo(); + Environment::setMemoryLimitMax(-1); + Environment::increaseMemoryLimitTo(-1); + + Versioned::withVersionedMode(function () use ($item): void { + Versioned::set_stage(Versioned::DRAFT); + + $logger = new Queue\Logger(); + $logger->setJob($this); + + $folder = $item + ? File::get()->byID($item) + : Folder::create(); + + if ($folder === null) { + throw new RuntimeException(sprintf('Legacy folder not found for file %d', $item)); + } + + $result = Helper::create() + ->setLogger($logger) + ->migrateFolder(singleton(AssetStore::class), $folder); + + if (count($result) > 0) { + return; + } + + $this->addMessage(sprintf('Nothing moved for folder for file %d', $item)); + }); + } +} diff --git a/src/FileMigration/LegacyThumbnail/Task.php b/src/FileMigration/LegacyThumbnail/Task.php new file mode 100644 index 00000000..e5d4af18 --- /dev/null +++ b/src/FileMigration/LegacyThumbnail/Task.php @@ -0,0 +1,72 @@ +getItemsToProcess(); + $this->queueJobsFromIds($request, $ids, Job::class, self::CHUNK_SIZE); + } + + /** + * Code taken from @see LegacyThumbnailMigrationHelper::run() + * + * @return array + */ + private function getItemsToProcess(): array + { + // Check if the File dataobject has a "Filename" field. + // If not, cannot migrate + if (!DB::get_schema()->hasField('File', 'Filename')) { + return []; + } + + return Versioned::withVersionedMode(static function (): array { + Versioned::set_stage(Versioned::DRAFT); + + // we start with just the root folder + $ids = [0]; + + // Migrate all nested folders + $folders = Helper::create() + ->getFolderQuery() + ->sort('ID', 'ASC') + ->columnUnique('ID'); + + foreach ($folders as $id) { + if (!$id) { + continue; + } + + $ids[] = (int) $id; + } + + return $ids; + }); + } +} diff --git a/src/FileMigration/SecureAssets/Helper.php b/src/FileMigration/SecureAssets/Helper.php new file mode 100644 index 00000000..90b83b9d --- /dev/null +++ b/src/FileMigration/SecureAssets/Helper.php @@ -0,0 +1,16 @@ +items = $this->getItemsToProcess(); + + parent::setup(); + } + + /** + * Code taken from @see SecureAssetsMigrationHelper::run() + * + * @param mixed $item + */ + protected function processItem($item): void + { + $logger = new Queue\Logger(); + $logger->setJob($this); + + $helper = Helper::create() + ->setLogger($logger); + + /** @var Folder $folder */ + $folder = Folder::get()->byID($item); + + if (!$folder) { + $this->addMessage(sprintf('No Folder record found for ID %d. Skipping', $item)); + + return; + } + + $store = singleton(AssetStore::class); + $filesystem = $store->getPublicFilesystem(); + + $result = $helper->migrateFolder($filesystem, $folder->getFilename()); + + if ($result) { + return; + } + + $this->addMessage(sprintf('No action needed for Folder ID %d. Skipping', $item)); + } + + /** + * Code taken from @see SecureAssetsMigrationHelper::run() + * + * @return array + */ + private function getItemsToProcess(): array + { + $fileTable = DataObject::getSchema()->baseDataTable(File::class); + $securedFolders = SQLSelect::create() + ->setFrom(sprintf('"%s"', $fileTable)) + ->setSelect([ + '"ID"', + '"FileFilename"', + ]) + ->addWhere([ + '"ClassName" = ?' => Folder::class, + // We don't need to check 'Inherited' permissions, + // since Apache applies parent .htaccess and the module doesn't create them in this case. + // See SecureFileExtension->needsAccessFile() + '"CanViewType" IN(?,?)' => ['LoggedInUsers', 'OnlyTheseUsers'], + ]); + + $items = []; + + foreach ($securedFolders->execute()->map() as $id => $path) { + if (!$id) { + continue; + } + + $items[] = (int) $id; + } + + return $items; + } +} diff --git a/src/FileMigration/SecureAssets/Task.php b/src/FileMigration/SecureAssets/Task.php new file mode 100644 index 00000000..2897a699 --- /dev/null +++ b/src/FileMigration/SecureAssets/Task.php @@ -0,0 +1,33 @@ +queueJob($job); + } +} diff --git a/src/FileMigration/TagsToShortCode/Helper.php b/src/FileMigration/TagsToShortCode/Helper.php new file mode 100644 index 00000000..cb58ffa0 --- /dev/null +++ b/src/FileMigration/TagsToShortCode/Helper.php @@ -0,0 +1,62 @@ + $ids] + ); + + $records = $query->execute(); + $this->rewriteFieldForRecords($records, $table, $field); + } + + public function getFieldMap(// phpcs:ignore SlevomatCodingStandard.TypeHints + $baseClass, + $includeBaseClass, + $fieldNames + ): array { + // public scope change + return parent::getFieldMap($baseClass, $includeBaseClass, $fieldNames); + } + + /** + * Override for @see TagsToShortcodeHelper::updateTagFromFile() + * + * Remove empty attributes + * this remedies the issue with WYSIWYG editor not rendering assets with empty attribute + * + * @param string $tag + * @param File $file + * @return string + */ + protected function updateTagFromFile($tag, File $file)// phpcs:ignore SlevomatCodingStandard.TypeHints + { + return str_replace(['title=""', 'alt=""'], ['', ''], $tag); + } +} diff --git a/src/FileMigration/TagsToShortCode/Job.php b/src/FileMigration/TagsToShortCode/Job.php new file mode 100644 index 00000000..14eb2b2a --- /dev/null +++ b/src/FileMigration/TagsToShortCode/Job.php @@ -0,0 +1,48 @@ +items = $items; + } + + /** + * Code taken from @see TagsToShortcodeHelper::run() + * + * @param mixed $item + */ + protected function processItem($item): void + { + $table = array_shift($item); + $field = array_shift($item); + $id = array_shift($item); + + $logger = new Queue\Logger(); + $logger->setJob($this); + + $helper = Helper::create(); + $helper->setLogger($logger); + + // Update table + $helper->updateTable($table, $field, [$id]); + } +} diff --git a/src/FileMigration/TagsToShortCode/Legacy/Helper.php b/src/FileMigration/TagsToShortCode/Legacy/Helper.php new file mode 100644 index 00000000..5280da10 --- /dev/null +++ b/src/FileMigration/TagsToShortCode/Legacy/Helper.php @@ -0,0 +1,50 @@ +getResolutionFileIDHelpers() as $fileIDHelper) { + $parsedFileID = $fileIDHelper->parseFileID($fileID); + + if (!$parsedFileID) { + continue; + } + + $filename = $parsedFileID->getFilename(); + $filename = $this->fixFilename($filename); + $parsedFileID = $parsedFileID->setFilename($filename); + + $foundTuple = $this->searchForTuple($parsedFileID, $filesystem, true); + + if ($foundTuple) { + return $foundTuple; + } + } + + // If we couldn't resolve the file ID, we bail + return null; + } + + /** + * Fetch correct filename from database instead of relying on filename from asset reference + * this fixes case sensitivity errors + * + * @param string $filename + * @return string + */ + private function fixFilename(string $filename): string + { + if (!$filename) { + return $filename; + } + + $query = SQLSelect::create( + '"FileFilename"', + '"File"', + sprintf('LCASE("FileFilename") = %s', Convert::raw2sql(mb_strtolower($filename), true)), + ['"ID"' => 'ASC'], + [], + [], + 1 + ); + + $results = $query->execute(); + $result = $results->first(); + + if (!array_key_exists('FileFilename', $result) || !$result['FileFilename']) { + return $filename; + } + + return (string) $result['FileFilename']; + } +} diff --git a/src/FileMigration/TagsToShortCode/Task.php b/src/FileMigration/TagsToShortCode/Task.php new file mode 100644 index 00000000..3207adfd --- /dev/null +++ b/src/FileMigration/TagsToShortCode/Task.php @@ -0,0 +1,124 @@ +getTableFields(); + $items = []; + + foreach ($fields as $data) { + $table = array_shift($data); + $field = array_shift($data); + + $query = SQLSelect::create('"ID"', sprintf('"%s"', $table), [], ['ID' => 'ASC']); + $results = $query->execute(); + + while ($result = $results->next()) { + $id = (int) $result['ID']; + $items[] = [ + $table, + $field, + $id, + ]; + } + } + + $this->queueJobsFromData($request, $items, Job::class, self::CHUNK_SIZE); + } + + /** + * Code taken from @see TagsToShortcodeHelper::run() + * + * @return array + * @throws ReflectionException + */ + private function getTableFields(): array + { + $helper = Helper::create(); + Environment::increaseTimeLimitTo(); + + $classes = $helper->getFieldMap(DataObject::class, false, [ + 'HTMLText', + 'HTMLVarchar', + ]); + + $versioned = singleton(Versioned::class); + $tableList = DB::table_list(); + $items = []; + + foreach ($classes as $class => $tables) { + /** @var DataObject|Versioned $singleton */ + $singleton = singleton($class); + $hasVersions = + $singleton->hasExtension(Versioned::class) && + $singleton->hasStages(); + + foreach ($tables as $table => $fields) { + foreach ($fields as $field) { + + /** @var DBField $dbField */ + $dbField = DataObject::singleton($class)->dbObject($field); + + if ($dbField && + $dbField->hasMethod('getProcessShortcodes') && + !$dbField->getProcessShortcodes()) { + continue; + } + + if (!isset($tableList[strtolower($table)])) { + // When running unit test some tables won't be created. We'll just skip those. + continue; + } + + $items[] = [ + $table, + $field, + ]; + + if (!$hasVersions) { + continue; + } + + $items[] = [ + $versioned->stageTable($table, Versioned::LIVE), + $field, + ]; + } + } + } + + return $items; + } +} diff --git a/src/FileMigration/Usage/Report.php b/src/FileMigration/Usage/Report.php new file mode 100644 index 00000000..af8b3ab4 --- /dev/null +++ b/src/FileMigration/Usage/Report.php @@ -0,0 +1,221 @@ + '', + 'Live' => '_Live', + ]; + + foreach ($stages as $label => $suffix) { + $this->getAssetReportForStage($suffix, $label); + } + } + + public function findAssets(string $html): array + { + $ids = []; + $matches = []; + preg_match_all('/\[file_link,id=(\d+)\]/', $html, $matches); + + if (array_key_exists(1, $matches)) { + foreach ($matches[1] as $id) { + if (!$id) { + continue; + } + + $ids[] = (int) $id; + } + } + + $ids = array_unique($ids); + sort($ids, SORT_NUMERIC); + $ids = array_values($ids); + + return $ids; + } + + private function getAssetReportForStage(string $suffix, string $label): void + { + echo sprintf('%d - All assets
', $count); + + $query = sprintf( + 'SELECT COUNT(`ID`) as `count` FROM `File%s`' + . ' WHERE `FileFilename` LIKE \'%%_generated_pdfs/%%\' AND `Title` LIKE \'%%.pdf\'', + $suffix + ); + + $results = DB::query($query); + $result = $results->first(); + $count = array_key_exists('count', $result) + ? $result['count'] + : 0; + + echo sprintf('%d - PDF assets
', $count); + + $query = sprintf( + 'SELECT COUNT(`ID`) as `count` FROM `File%s`' + . ' WHERE `FileFilename` LIKE \'%%_generated_pdfs/%%\' AND `Title` LIKE \'%%.html\'', + $suffix + ); + + $results = DB::query($query); + $result = $results->first(); + $count = array_key_exists('count', $result) + ? $result['count'] + : 0; + + echo sprintf('%d - HTML assets
', $count); + + $query = sprintf( + 'SELECT COUNT(`ID`) as `count` FROM `File%s`' + . ' WHERE `FileFilename` LIKE \'%%_generated_pdfs/%%\'' + . ' AND `Title` NOT LIKE \'%%.pdf\' AND `Title` NOT LIKE \'%%.html\'', + $suffix + ); + + $results = DB::query($query); + $result = $results->first(); + $count = array_key_exists('count', $result) + ? $result['count'] + : 0; + + echo sprintf('%d - Other assets
', $count); + + echo '%d - News letter page
', $count); + + + echo '%d - %s / %s
', $count, $table, $field); + + if ($count === 0) { + continue; + } + + echo sprintf( + '%s - Asset IDs for %s / %s %d
', + implode(', ', $foundIds), + $table, + $field, + $objectId + ); + } + } +} diff --git a/src/Report/CorruptedAssetsReport.php b/src/Report/CorruptedAssetsReport.php new file mode 100644 index 00000000..8c3deae8 --- /dev/null +++ b/src/Report/CorruptedAssetsReport.php @@ -0,0 +1,63 @@ +filter('FileHash', null) + ->exclude('ClassName', Folder::class); + }); + } + + public function columns(): array + { + return [ + 'ID' => [ + 'title' => 'ID', + 'formatting' => '$ID', + ], + 'Title' => [ + 'title' => 'Asset name', + 'link' => static function ($value, $item) { + /** @var File $item */ + return sprintf( + '%s', + Convert::raw2att($item->CMSEditLink()), + Convert::raw2att($value), + Convert::raw2xml($value) + ); + }, + ], + ]; + } +} diff --git a/src/Report/LegacyAssetsReport.php b/src/Report/LegacyAssetsReport.php new file mode 100644 index 00000000..97bae106 --- /dev/null +++ b/src/Report/LegacyAssetsReport.php @@ -0,0 +1,130 @@ +]* src="assets/\'', + // resampled assets references (debug only as this is a subset of the image tags) +// 'SELECT "ID" FROM "%1$s" WHERE "%2$s" LIKE \'%%_resampled/ResizedImage%%\'' +// . ' AND "%2$s" REGEXP \'(_resampled\/ResizedImage)+[a-zA-Z0-9]+[-]{1}\'' +// . ' AND "%2$s" NOT REGEXP \'(_resampled\/ResizedImage)+[a-zA-Z0-9]+[\/]{1}\'', + // image shortcodes with empty attribute (debug only as this is a subset of the image tags) +// 'SELECT "ID" FROM "%1$s" WHERE "%2$s" REGEXP \'[[]image[^]]*=""[^]]*[]]\'', + ]; + + public function title(): string + { + return 'Legacy asset references'; + } + + public function sourceRecords( // phpcs:ignore SlevomatCodingStandard.TypeHints + array $params = [], + $sort = null, + $limit = null + ): SS_List { + $ids = []; + + foreach (self::HTML_FIELDS as $fieldData) { + $table = array_shift($fieldData); + $field = array_shift($fieldData); + + foreach (self::QUERIES as $sql) { + $query = sprintf($sql, $table, $field); + $results = DB::query($query); + + while ($result = $results->next()) { + $id = (int) $result['ID']; + + if (!$id) { + continue; + } + + $ids[$id] = $id; + } + } + } + + $list = DataObject::get(SiteTree::class, null, $sort, null, $limit); + + if (count($ids) === 0) { + return $list->byIDs([0]); + } + + if ($sort === null) { + $list = $list->sort('ID', 'ASC'); + } + + return $list + ->exclude([ + 'ClassName' => RedirectorPage::class, + ]) + ->filter([ + 'ID' => array_values($ids), + ]); + } + + public function columns(): array + { + return [ + 'ID' => [ + 'title' => 'ID', + 'formatting' => '$ID', + ], + 'Type' => [ + 'title' => 'Type', + 'link' => static function ($value, $item) { + return ClassInfo::shortName($item); + }, + ], + 'State' => [ + 'title' => 'State', + 'link' => static function ($value, $item) { + /** @var $item SiteTree */ + if (!$item instanceof SiteTree) { + return 'Not published'; + } + + return $item->isPublished() + ? 'Published' + : 'Not published'; + }, + ], + 'Title' => [ + 'title' => 'Page title', + 'formatting' => + '$value', + ], + ]; + } +}