Skip to content

UpgradeWizard reference #29

@helsner

Description

@helsner

I created an upgradewizard which is not fully functional but implements absolute basic logic.
we might wanna reference that here.

Other than that i create the issue for people looking for help.
So - hi!

https://gist.github.com/helsner/37c274b157d3a598a04737e9256b2039

<?php

declare(strict_types=1);

namespace Your\Namespace\UpgradeWizard;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Updates\ChattyInterface;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;

/**
 * Class RedirectsUpgradeWizard.php
 *
 * Upgrade Wizard to perform a migration to the new redirects
 */
class UrlForwardingRedirectsUpgradeWizard implements UpgradeWizardInterface, ChattyInterface
{
    /**
     * @var string
     */
    protected $table = 'sys_redirect';

    /**
     * @var StreamOutput
     */
    protected $output;

    /**
     * Return the identifier for this wizard
     * This should be the same string as used in the ext_localconf class registration
     *
     * @return string
     */
    public function getIdentifier(): string
    {
        return 'url_forwarding_to_core_redirects';
    }

    /**
     * Return the speaking name of this wizard
     *
     * @return string
     */
    public function getTitle(): string
    {
        return 'Migrate EXT:url_forwarding redirects to EXT:redirects records';
    }

    /**
     * Return the description for this wizard
     *
     * @return string
     */
    public function getDescription(): string
    {
        return 'With v9 there are core redirects which make third party ext (mostly) obsolete.
        This wizard migrates EXT:url_forwarding redirects to core redirects.';
    }

    /**
     * Execute the update
     * Called when a wizard reports that an update is necessary
     *
     * @return bool
     */
    public function executeUpdate(): bool
    {
        if (ExtensionManagementUtility::isLoaded('redirects') === false) {
            $this->output->writeln('EXT:redirects is not installed. Aborting.');
            return false;
        }

        if ($this->coreTableExists() === false) {
            $this->output->writeln(
                'The database table of EXT:redirects do not exist. Please update your database'
                . ' tables first. DO NOT REMOVE anything yet.'
            );
            return false;
        }

        $this->migrateRecords();
        return true;
    }

    /**
     * Is an update necessary?
     * Is used to determine whether a wizard needs to be run.
     * Check if data for migration exists.
     *
     * @return bool
     */
    public function updateNecessary(): bool
    {
        $updateNeeded = false;
        // Check if the database table even exists
        if ($this->checkIfWizardIsRequired()) {
            $updateNeeded = true;
        }
        return $updateNeeded;
    }

    /**
     * Returns an array of class names of Prerequisite classes
     * This way a wizard can define dependencies like "database up-to-date" or
     * "reference index updated"
     *
     * @return string[]
     */
    public function getPrerequisites(): array
    {
        return [];
    }

    /**
     * Check if there are record within "tx_urlforwarding_domain_model_redirect" database table.
     *
     * @throws \InvalidArgumentException
     * @return bool
     */
    protected function checkIfWizardIsRequired(): bool
    {
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
        $queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
        $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
        $numberOfEntries = $queryBuilder
            ->count('uid')
            ->from('tx_urlforwarding_domain_model_redirect')
            // This is here in case you don't have the TCA of tx_urlforwarding_domain_model_redirect anymore, which is necessary for the DeletedRestriction
            ->where(
                $queryBuilder->expr()->eq('tx_urlforwarding_domain_model_redirect.deleted', 0)
            )
            ->execute()
            ->fetchColumn();
        return $numberOfEntries > 0;
    }

    /**
     * Setter injection for output into upgrade wizards
     *
     * @param OutputInterface $output
     */
    public function setOutput(OutputInterface $output): void
    {
        $this->output = $output;
    }

    /**
     * @return bool
     */
    protected function coreTableExists(): bool
    {
        $databaseConnection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
        return $databaseConnection->getSchemaManager()->tablesExist([$this->table]);
    }

    /**
     * @return void
     */
    protected function migrateRecords()
    {
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
        $queryBuilder = $connectionPool->getQueryBuilderForTable('tx_urlforwarding_domain_model_redirect');
        $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));

        $redirectsToMigrate = $queryBuilder
            ->select('tx_urlforwarding_domain_model_redirect.*', 'sys_domain.domainName')
            ->from('tx_urlforwarding_domain_model_redirect')
            ->leftJoin(
                'tx_urlforwarding_domain_model_redirect',
                'tx_urlforwarding_domain_mm',
                'tx_urlforwarding_domain_mm',
                $queryBuilder->expr()->eq(
                    'tx_urlforwarding_domain_model_redirect.uid',
                    $queryBuilder->quoteIdentifier('tx_urlforwarding_domain_mm.uid_local'),
                )
            )
            ->leftJoin(
                'tx_urlforwarding_domain_mm',
                'sys_domain',
                'sys_domain',
                $queryBuilder->expr()->eq(
                    'sys_domain.uid',
                    $queryBuilder->quoteIdentifier('tx_urlforwarding_domain_mm.uid_foreign'),
                )
            )
            // This is here in case you don't have the TCA of tx_urlforwarding_domain_model_redirect anymore, which is necessary for the DeletedRestriction
            ->where(
                $queryBuilder->expr()->eq('tx_urlforwarding_domain_model_redirect.deleted', 0)
            )
            ->execute()
            ->fetchAll();

        // v10change TODO: issues:
        // - handle regex
        // - handle full url as source path/forward url
        // - handle slash at the beginning (that seems working with or without)

        // TODO: the following block is depending on your data structure/usage etc.
        $skippedRedirects = [];
        foreach ($redirectsToMigrate as $redirect) {
            $skipRedirect = false;
            $target = '';
            switch ((int) $redirect['type']) {
                case 0:
                    if (!$redirect['internal_page']) {
                        $skipRedirect = true;
                        break;
                    }
                    $target = 't3://page?uid=' . $redirect['internal_page'];
                    break;
                case 1:
                    if (!$redirect['external_url']) {
                        $skipRedirect = true;
                        break;
                    }
                    $target = $redirect['external_url'];
                    break;
                case 2:
                    if (!$redirect['internal_file']) {
                        $skipRedirect = true;
                        break;
                    }
                    $target = '/' . $redirect['internal_file'];
                    break;
            }

            if ($skipRedirect) {
                $skippedRedirects[] = $redirect['uid'];
                continue;
            }

            $migratedRedirect = [
                'source_host' => $redirect['domainName'] ?? '*',
                'source_path' => preg_replace('/\(\.html\)\?/', '', $redirect['forward_url']),
                'target' => $target,
                'target_statuscode' => $redirect['http_status'],

                'hitcount' => $redirect['counter'],
                'lasthiton' => $redirect['last_hit'],

                'createdon' => $redirect['crdate'],
                'updatedon' => $redirect['tstamp'],
                'createdby' => $redirect['cruser_id'],
            ];

            $newRedirects[] = $migratedRedirect;
        }

        $keys = [
            'source_host',
            'source_path',
            'target',
            'target_statuscode',
            'hitcount',
            'lasthiton',
            'createdon',
            'updatedon',
            'createdby',
        ];

        $connectionPool->getConnectionForTable($this->table)->bulkInsert(
            $this->table,
            $newRedirects,
            $keys
        );

        if ($skippedRedirects) {
            $this->output->writeln('redirects skipped due to invalid data (no redirect target): ' . implode(', ',
                    $skippedRedirects));
        } else {
            $this->output->writeln('no redirects skipped, all migrated');
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions