Skip to content

Instantly share code, notes, and snippets.

@thommyhh
Created January 20, 2025 08:18
Show Gist options
  • Save thommyhh/7989c45db15245dbbb7533d0695a3383 to your computer and use it in GitHub Desktop.
Save thommyhh/7989c45db15245dbbb7533d0695a3383 to your computer and use it in GitHub Desktop.
Update image URLs for images in RTE fields in TYPO3 CMS
<?php
declare(strict_types=1);
namespace MyVendor\MyPackage\Install;
use Netresearch\RteCKEditorImage\Database\RteImagesDbHook;
use Symfony\Contracts\Service\Attribute\Required;
use TYPO3\CMS\Backend\Resource\PublicUrlPrefixer;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
use TYPO3\CMS\Core\Html\RteHtmlParser;
use TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Attribute\UpgradeWizard;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
#[UpgradeWizard('rte_image_url_upgrade')]
class RteImageUrlUpgradeWizard implements UpgradeWizardInterface
{
protected $title = 'RTE Image Upgrade Wizard';
protected $description = 'Fix URLs for images in RTE fields';
protected RteImagesDbHook $rteImagesDbHook;
protected RteHtmlParser $rteHtmlParser;
protected DataHandler $dataHandler;
protected ListenerProvider $listenerProvider;
#[Required]
public function setRteImagesDbHook(RteImagesDbHook $rteImagesDbHook): void
{
$this->rteImagesDbHook = $rteImagesDbHook;
}
#[Required]
public function setRteHtmlParser(RteHtmlParser $rteHtmlParser): void
{
$this->rteHtmlParser = $rteHtmlParser;
}
#[Required]
public function setDataHandler(DataHandler $dataHandler): void
{
$this->dataHandler = $dataHandler;
}
#[Required]
public function setListenerProvider(ListenerProvider $listenerProvider): void
{
$this->listenerProvider = $listenerProvider;
$this->listenerProvider->addListener(GeneratePublicUrlForResourceEvent::class, PublicUrlPrefixer::class, 'prefixWithSitePath');
}
public function getTitle(): string
{
return $this->title;
}
public function getDescription(): string
{
return $this->description;
}
public function executeUpdate(): bool
{
$backupRequest = $GLOBALS['TYPO3_REQUEST'] ?? null;
// Find site `main`
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByIdentifier('main');
// Fake backend request to make sure RTE images are processed
/** @var \TYPO3\CMS\Core\Http\ServerRequest $request */
$request = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\ServerRequest::class);
$request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE)
->withUri($site->getBase()->withPath('/typo3/index.php'));
$GLOBALS['TYPO3_REQUEST'] = $request;
Bootstrap::initializeBackendUser(request: $request);
$GLOBALS['BE_USER']->user['admin'] = 1;
foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
$fieldsToFetch = [];
foreach ($tableConfiguration['columns'] as $fieldName => $fieldConfiguration) {
if ($fieldConfiguration['config']['type'] === 'text' && ($fieldConfiguration['config']['enableRichtext'] ?? false)) {
$fieldsToFetch[] = $fieldName;
}
}
if (!empty($fieldsToFetch)) {
$this->processRecords($tableName, $fieldsToFetch);
}
if ($GLOBALS['TCA'][$tableName]['ctrl']['type'] ?? false) {
foreach ($tableConfiguration['types'] as $type => $typeConfiguration) {
$fieldsToFetch = [];
foreach ($typeConfiguration['columnsOverrides'] ?? [] as $fieldName => $fieldConfiguration) {
if ($fieldConfiguration['config']['enableRichtext'] ?? false) {
$fieldsToFetch[] = $fieldName;
}
}
if (!empty($fieldsToFetch)) {
$this->processRecords($tableName, $fieldsToFetch, $type);
}
}
}
}
if ($backupRequest) {
$GLOBALS['TYPO3_REQUEST'] = $backupRequest;
}
return true;
}
protected function processRecords(string $table, array $fieldsToFetch, ?string $type = null): void
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder
->select('uid', ...$fieldsToFetch)
->from($table);
if ($type) {
$queryBuilder->addSelect($GLOBALS['TCA'][$table]['ctrl']['type']);
$queryBuilder->where(
$queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['type'], $queryBuilder->createNamedParameter($type))
);
}
$updateQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$updateQueryBuilder->update($table)
->where(
$updateQueryBuilder->expr()->eq('uid', $updateQueryBuilder->createNamedParameter('', \PDO::PARAM_INT, ':uid'))
);
foreach ($fieldsToFetch as $fieldName) {
if ($fieldName !== 'uid') {
$updateQueryBuilder->set($fieldName, $updateQueryBuilder->createNamedParameter('', \PDO::PARAM_STR, ':' . $fieldName), false);
}
}
foreach ($queryBuilder->executeQuery()->fetchAllAssociative() as $record) {
$incomingData = $record;
$this->dataHandler->checkValue_currentRecord = $record;
$this->rteImagesDbHook->processDatamap_postProcessFieldArray('update', $table, (string) $record['uid'], $record, $this->dataHandler);
if (count(array_diff($incomingData, $record)) > 0) {
foreach ($fieldsToFetch as $fieldName) {
if (!empty($record[$fieldName])) {
$updateQueryBuilder->setParameter($fieldName, $record[$fieldName]);
}
}
$updateQueryBuilder->setParameter('uid', $record['uid']);
$updateQueryBuilder->executeStatement();
}
}
}
public function updateNecessary(): bool
{
return !Environment::isCli();
}
public function getPrerequisites(): array
{
return [DatabaseUpdatedPrerequisite::class];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment