Skip to content

UpgradeWizard reference #29

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');
        }
    }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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