first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\Connection\TerUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\InstallUtility;
|
||||
use TYPO3\CMS\Extensionmanager\Utility\ListUtility;
|
||||
|
||||
/**
|
||||
* Download extension from TER
|
||||
*/
|
||||
abstract class AbstractDownloadExtensionUpdate implements UpgradeWizardInterface, ConfirmableInterface, ChattyInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $repositoryUrl = 'https://typo3.org/fileadmin/ter/@filename';
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\ExtensionModel
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the update
|
||||
* Called when a wizard reports that an update is necessary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
return $this->installExtension($this->extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be called to install an extension following all proper processes
|
||||
* (e.g. installing in extList, respecting priority, etc.)
|
||||
*
|
||||
* @param \TYPO3\CMS\Install\Updates\ExtensionModel $extension
|
||||
* @return bool whether the installation worked or not
|
||||
* @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
|
||||
*/
|
||||
protected function installExtension(ExtensionModel $extension): bool
|
||||
{
|
||||
$updateSuccessful = true;
|
||||
/** @var ObjectManager $objectManager */
|
||||
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
|
||||
|
||||
/** @var ListUtility $extensionListUtility */
|
||||
$extensionListUtility = $objectManager->get(ListUtility::class);
|
||||
$availableExtensions = $extensionListUtility->getAvailableExtensions();
|
||||
|
||||
$extensionKey = $extension->getKey();
|
||||
$isExtensionAvailable = !empty($availableExtensions[$extensionKey]);
|
||||
$isComposerMode = Environment::isComposerMode();
|
||||
|
||||
if (!$isComposerMode && !$isExtensionAvailable) {
|
||||
/** @var TerUtility $extensionTerUtility */
|
||||
$extensionTerUtility = $objectManager->get(TerUtility::class);
|
||||
$t3xContent = $this->fetchExtension($extensionKey, $extension->getVersionString());
|
||||
if (empty($t3xContent)) {
|
||||
$updateSuccessful = false;
|
||||
$this->output->writeln('<error>The extension ' . $extensionKey . ' could not be downloaded.</error>');
|
||||
}
|
||||
$t3xExtracted = $extensionTerUtility->decodeExchangeData($t3xContent);
|
||||
if (empty($t3xExtracted) || !is_array($t3xExtracted) || empty($t3xExtracted['extKey'])) {
|
||||
$updateSuccessful = false;
|
||||
$this->output->writeln('<error>The extension ' . $extensionKey . ' could not be extracted.</error>');
|
||||
}
|
||||
|
||||
/** @var FileHandlingUtility $extensionFileHandlingUtility */
|
||||
$extensionFileHandlingUtility = $objectManager->get(FileHandlingUtility::class);
|
||||
$extensionFileHandlingUtility->unpackExtensionFromExtensionDataArray($t3xExtracted);
|
||||
|
||||
// The listUtility now needs to have the regenerated list of packages
|
||||
$extensionListUtility->reloadAvailableExtensions();
|
||||
}
|
||||
|
||||
if ($isComposerMode && !$isExtensionAvailable) {
|
||||
$updateSuccessful = false;
|
||||
$this->output->writeln(
|
||||
'<warning>The extension ' . $extensionKey
|
||||
. ' can not be downloaded since Composer is used for package management. Please require this '
|
||||
. 'extension as package via Composer: "composer require ' . $extension->getComposerName()
|
||||
. ':^' . $extension->getVersionString() . '"</warning>'
|
||||
);
|
||||
}
|
||||
|
||||
if ($updateSuccessful) {
|
||||
/** @var InstallUtility $extensionInstallUtility */
|
||||
$extensionInstallUtility = $objectManager->get(InstallUtility::class);
|
||||
$extensionInstallUtility->install($extensionKey);
|
||||
}
|
||||
|
||||
return $updateSuccessful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch extension from repository
|
||||
*
|
||||
* @param string $extensionKey The extension key to fetch
|
||||
* @param string $version The version to fetch
|
||||
* @throws \InvalidArgumentException
|
||||
* @return string T3X file content
|
||||
*/
|
||||
protected function fetchExtension($extensionKey, $version): string
|
||||
{
|
||||
if (empty($extensionKey) || empty($version)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'No extension key for fetching an extension was given.',
|
||||
1344687432
|
||||
);
|
||||
}
|
||||
|
||||
$filename = $extensionKey[0] . '/' . $extensionKey[1] . '/' . $extensionKey . '_' . $version . '.t3x';
|
||||
$url = str_replace('@filename', $filename, $this->repositoryUrl);
|
||||
|
||||
return $this->fetchUrl($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an URL and return the response
|
||||
* This wrapper method is required to try several download methods if
|
||||
* the configuration is not valid or initially written by the installer.
|
||||
*
|
||||
* @param string $url The URL to file
|
||||
* @throws \Exception
|
||||
* @throws \InvalidArgumentException
|
||||
* @return string File content
|
||||
*/
|
||||
protected function fetchUrl($url): string
|
||||
{
|
||||
if (empty($url)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'No URL for downloading an extension given.',
|
||||
1344687436
|
||||
);
|
||||
}
|
||||
|
||||
$fileContent = GeneralUtility::getUrl($url);
|
||||
|
||||
// Can not fetch url, throw an exception
|
||||
if ($fileContent === false) {
|
||||
throw new \RuntimeException(
|
||||
'Can not fetch URL "' . $url . '". Possible reasons are network problems or misconfiguration.',
|
||||
1344685036
|
||||
);
|
||||
}
|
||||
|
||||
return $fileContent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Generic class that every update wizard class inherits from.
|
||||
* Used by the update wizard in the install tool.
|
||||
*
|
||||
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
*/
|
||||
abstract class AbstractUpdate implements UpgradeWizardInterface, ChattyInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
trigger_error('Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this), E_USER_DEPRECATED);
|
||||
}
|
||||
/**
|
||||
* The human-readable title of the upgrade wizard
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* The update wizard identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* User input, set from outside
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $userInput;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* Returns the title attribute
|
||||
*
|
||||
* @deprecated Deprecated since TYPO3 v9
|
||||
* @return string The title of this update wizard
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
if ($this->title) {
|
||||
return $this->title;
|
||||
}
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title attribute
|
||||
*
|
||||
* @param string $title The title of this update wizard
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of this class
|
||||
*
|
||||
* @return string The identifier of this update wizard
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return $this->identifier ?? static::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier attribute
|
||||
*
|
||||
* @param string $identifier The identifier of this update wizard
|
||||
*/
|
||||
public function setIdentifier($identifier)
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that helps dealing with the compatibility
|
||||
* layer that some update wizards don't have a second parameter
|
||||
* thus, it evaluates everything already
|
||||
*
|
||||
* @return bool If the wizard should be shown at all on the overview page
|
||||
* @see checkForUpdate()
|
||||
*/
|
||||
public function shouldRenderWizard()
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$explanation = '';
|
||||
$result = $this->checkForUpdate($explanation);
|
||||
return (bool)$result === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given table exists
|
||||
*
|
||||
* @param string $table
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkIfTableExists($table)
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$tableExists = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable($table)
|
||||
->getSchemaManager()
|
||||
->tablesExist([$table]);
|
||||
|
||||
return $tableExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether updates are required.
|
||||
*
|
||||
* @param string &$description The description for the update
|
||||
* @return bool Whether an update is required (TRUE) or not (FALSE)
|
||||
*/
|
||||
abstract public function checkForUpdate(&$description);
|
||||
|
||||
/**
|
||||
* Performs the accordant updates.
|
||||
*
|
||||
* @param array &$dbQueries Queries done in this update
|
||||
* @param string &$customMessage Custom message
|
||||
* @return bool Whether everything went smoothly or not
|
||||
*/
|
||||
abstract public function performUpdate(array &$dbQueries, &$customMessage);
|
||||
|
||||
/**
|
||||
* This method can be called to install extensions following all proper processes
|
||||
* (e.g. installing in extList, respecting priority, etc.)
|
||||
*
|
||||
* @param array $extensionKeys List of keys of extensions to install
|
||||
*/
|
||||
protected function installExtensions(array $extensionKeys)
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
/** @var \TYPO3\CMS\Extensionmanager\Utility\InstallUtility $installUtility */
|
||||
$installUtility = GeneralUtility::makeInstance(
|
||||
\TYPO3\CMS\Extensionmanager\Utility\InstallUtility::class
|
||||
);
|
||||
$installUtility->install($extensionKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks some wizard as being "seen" so that it not shown again.
|
||||
*
|
||||
* Writes the info in LocalConfiguration.php
|
||||
*
|
||||
* @param mixed $confValue The configuration is set to this value
|
||||
*/
|
||||
protected function markWizardAsDone($confValue = 1)
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
GeneralUtility::makeInstance(Registry::class)->set('installUpdate', static::class, $confValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this wizard has been "done" before
|
||||
*
|
||||
* @return bool TRUE if wizard has been done before, FALSE otherwise
|
||||
*/
|
||||
protected function isWizardDone()
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$wizardClassName = static::class;
|
||||
return GeneralUtility::makeInstance(Registry::class)->get('installUpdate', $wizardClassName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the update
|
||||
* Called when a wizard reports that an update is necessary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$queries = [];
|
||||
$message = '';
|
||||
$result = $this->performUpdate($queries, $message);
|
||||
$this->output->write($message);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$description = '';
|
||||
$result = $this->checkForUpdate($description);
|
||||
$this->output->write($description);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter injection for output into upgrade wizards
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
trigger_error(
|
||||
'Deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, use UpgradeWizardInterface directly. affected class: ' . get_class($this),
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$this->output = $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
|
||||
/**
|
||||
* Installs EXT:adminpanel
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class AdminPanelInstall extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'adminpanel',
|
||||
'TYPO3 Admin Panel',
|
||||
'9.2',
|
||||
'typo3/cms-adminpanel',
|
||||
'The TYPO3 admin panel provides a panel with additional functionality in the frontend (Debugging, Caching, Preview...)'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'You should install the "adminpanel" only if needed. ' . $this->extension->getDescription(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'adminpanelExtension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install extension "adminpanel"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The TYPO3 admin panel was extracted to an own extension. This update installs the extension.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !ExtensionManagementUtility::isLoaded('adminpanel');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
|
||||
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Informational upgrade wizard to remind upgrading instances
|
||||
* may have to verify argon2i is available on the live servers
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class Argon2iPasswordHashes implements UpgradeWizardInterface, ConfirmableInterface
|
||||
{
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->confirmation = new Confirmation(
|
||||
'Please make sure to read the following carefully:',
|
||||
$this->getDescription(),
|
||||
false,
|
||||
'Yes, I understand!',
|
||||
'',
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'argon2iPasswordHashes';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Reminder to verify live system supports argon2i';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'TYPO3 uses the modern hash mechanism "argon2i" on this system. Existing passwords'
|
||||
. ' will be automatically upgraded to this mechanism upon user login. If this instance'
|
||||
. ' is later deployed to a different system, make sure the system does support argon2i'
|
||||
. ' too, otherwise logins will fail. If that is not possible, select a different hash'
|
||||
. ' algorithm in Setting > Presets > Password hashing settings and make sure no user'
|
||||
. ' has been upgraded yet. This upgrade wizard exists only to inform you, it does not'
|
||||
. ' change the system';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether updates are required.
|
||||
*
|
||||
* @return bool Whether an update is required (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$passwordHashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
|
||||
$feHash = $passwordHashFactory->getDefaultHashInstance('BE');
|
||||
$beHash = $passwordHashFactory->getDefaultHashInstance('FE');
|
||||
return $feHash instanceof Argon2iPasswordHash || $beHash instanceof Argon2iPasswordHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This upgrade wizard has informational character only, it does not perform actions.
|
||||
*
|
||||
* @return bool Whether everything went smoothly or not
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/**
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
||||
use TYPO3\CMS\Core\Resource\StorageRepository;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* Upgrade wizard which goes through all files referenced in backend_layout.icon
|
||||
* and creates sys_file records as well as sys_file_reference records for each hit.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class BackendLayoutIconUpdateWizard implements UpgradeWizardInterface, ChattyInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var ResourceStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Table to migrate records from
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'backend_layout';
|
||||
|
||||
/**
|
||||
* Table field holding the migration to be
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldToMigrate = 'icon';
|
||||
|
||||
/**
|
||||
* the source file resides here
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sourcePath = 'uploads/media/';
|
||||
|
||||
/**
|
||||
* target folder after migration
|
||||
* Relative to fileadmin
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $targetPath = '_migrated/backend_layouts/';
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'backendLayoutIcons';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate all file relations from backend_layout.icon to sys_file_references';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'This update wizard goes through all files that are referenced in the'
|
||||
. ' backend_layout.icon field and adds the files to the FAL File Index.'
|
||||
. ' It also moves the files from uploads/ to the fileadmin/_migrated/ path.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if there are records to update
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !empty($this->getRecordsFromTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the configuration update.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$result = true;
|
||||
try {
|
||||
$storages = GeneralUtility::makeInstance(StorageRepository::class)->findAll();
|
||||
$this->storage = $storages[0];
|
||||
$records = $this->getRecordsFromTable();
|
||||
foreach ($records as $record) {
|
||||
$this->migrateField($record);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// If something goes wrong, migrateField() logs an error
|
||||
$result = false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records from table where the field to migrate is not empty (NOT NULL and != '')
|
||||
* and also not numeric (which means that it is migrated)
|
||||
*
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getRecordsFromTable()
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
try {
|
||||
return $queryBuilder
|
||||
->select('uid', 'pid', $this->fieldToMigrate)
|
||||
->from($this->table)
|
||||
->where(
|
||||
$queryBuilder->expr()->isNotNull($this->fieldToMigrate),
|
||||
$queryBuilder->expr()->neq(
|
||||
$this->fieldToMigrate,
|
||||
$queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->comparison(
|
||||
'CAST(CAST(' . $queryBuilder->quoteIdentifier($this->fieldToMigrate) . ' AS DECIMAL) AS CHAR)',
|
||||
ExpressionBuilder::NEQ,
|
||||
'CAST(' . $queryBuilder->quoteIdentifier($this->fieldToMigrate) . ' AS CHAR)'
|
||||
)
|
||||
)
|
||||
->orderBy('uid')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
} catch (DBALException $e) {
|
||||
throw new \RuntimeException(
|
||||
'Database query failed. Error was: ' . $e->getPrevious()->getMessage(),
|
||||
1511950673
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates a single field.
|
||||
*
|
||||
* @param array $row
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function migrateField($row)
|
||||
{
|
||||
$fieldItems = GeneralUtility::trimExplode(',', $row[$this->fieldToMigrate], true);
|
||||
if (empty($fieldItems) || is_numeric($row[$this->fieldToMigrate])) {
|
||||
return;
|
||||
}
|
||||
$fileadminDirectory = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/';
|
||||
$i = 0;
|
||||
|
||||
$storageUid = (int)$this->storage->getUid();
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
|
||||
foreach ($fieldItems as $item) {
|
||||
$fileUid = null;
|
||||
$sourcePath = Environment::getPublicPath() . '/' . $this->sourcePath . $item;
|
||||
$targetDirectory = Environment::getPublicPath() . '/' . $fileadminDirectory . $this->targetPath;
|
||||
$targetPath = $targetDirectory . PathUtility::basenameDuringBootstrap($item);
|
||||
|
||||
// maybe the file was already moved, so check if the original file still exists
|
||||
if (file_exists($sourcePath)) {
|
||||
if (!is_dir($targetDirectory)) {
|
||||
GeneralUtility::mkdir_deep($targetDirectory);
|
||||
}
|
||||
|
||||
// see if the file already exists in the storage
|
||||
$fileSha1 = sha1_file($sourcePath);
|
||||
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$existingFileRecord = $queryBuilder->select('uid')->from('sys_file')->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'sha1',
|
||||
$queryBuilder->createNamedParameter($fileSha1, \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'storage',
|
||||
$queryBuilder->createNamedParameter($storageUid, \PDO::PARAM_INT)
|
||||
)
|
||||
)->execute()->fetch();
|
||||
|
||||
// the file exists, the file does not have to be moved again
|
||||
if (is_array($existingFileRecord)) {
|
||||
$fileUid = $existingFileRecord['uid'];
|
||||
} else {
|
||||
// just move the file (no duplicate)
|
||||
rename($sourcePath, $targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
if ($fileUid === null) {
|
||||
// get the File object if it hasn't been fetched before
|
||||
try {
|
||||
// if the source file does not exist, we should just continue, but leave a message in the docs;
|
||||
// ideally, the user would be informed after the update as well.
|
||||
/** @var File $file */
|
||||
$file = $this->storage->getFile($this->targetPath . $item);
|
||||
$fileUid = $file->getUid();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// no file found, no reference can be set
|
||||
$this->logger->notice(
|
||||
'File ' . $this->sourcePath . $item . ' does not exist. Reference was not migrated.',
|
||||
[
|
||||
'table' => $this->table,
|
||||
'record' => $row,
|
||||
'field' => $this->fieldToMigrate,
|
||||
]
|
||||
);
|
||||
$format = 'File \'%s\' does not exist. Referencing field: %s.%d.%s. The reference was not migrated.';
|
||||
$this->output->writeln(sprintf(
|
||||
$format,
|
||||
$this->sourcePath . $item,
|
||||
$this->table,
|
||||
$row['uid'],
|
||||
$this->fieldToMigrate
|
||||
));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fileUid > 0) {
|
||||
$fields = [
|
||||
'fieldname' => $this->fieldToMigrate,
|
||||
'table_local' => 'sys_file',
|
||||
'pid' => $this->table === 'pages' ? $row['uid'] : $row['pid'],
|
||||
'uid_foreign' => $row['uid'],
|
||||
'uid_local' => $fileUid,
|
||||
'tablenames' => $this->table,
|
||||
'crdate' => time(),
|
||||
'tstamp' => time(),
|
||||
'sorting' => $i + 256,
|
||||
'sorting_foreign' => $i,
|
||||
];
|
||||
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
|
||||
$queryBuilder->insert('sys_file_reference')->values($fields)->execute();
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
|
||||
// Update referencing table's original field to now contain the count of references,
|
||||
// but only if all new references could be set
|
||||
if ($i === count($fieldItems)) {
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
|
||||
$queryBuilder->update($this->table)->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)->set($this->fieldToMigrate, $i)->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Update the backend user "uc" array to use arrays for its structure, as old TYPO3 versions sometimes used stdClasses
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class BackendUserConfigurationUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'backendUsersConfiguration';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Update backend user configuration array';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The backend user "uc" array, which is persisted in the db, now only allows for'
|
||||
. ' arrays inside its structure instead of stdClass objects.'
|
||||
. ' Update the uc structure for all backend users.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$needsExecution = false;
|
||||
|
||||
foreach ($this->getAffectedBackendUsers() as $backendUser) {
|
||||
$userConfig = $this->unserializeUserConfig($backendUser['uc']);
|
||||
|
||||
if (!is_array($userConfig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_walk_recursive($userConfig, function (&$item) use (&$needsExecution) {
|
||||
if ($item instanceof \stdClass) {
|
||||
$needsExecution = true;
|
||||
}
|
||||
});
|
||||
|
||||
if ($needsExecution) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $needsExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update for be_users
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
foreach ($this->getAffectedBackendUsers() as $backendUser) {
|
||||
$userConfig = $this->unserializeUserConfig($backendUser['uc']);
|
||||
|
||||
if (!is_array($userConfig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_walk_recursive($userConfig, function (&$item) {
|
||||
if ($item instanceof \stdClass) {
|
||||
$item = json_decode(json_encode($item), true);
|
||||
}
|
||||
});
|
||||
|
||||
$this->updateBackendUser((int)$backendUser['uid'], $userConfig);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function unserializeUserConfig(string $userConfig)
|
||||
{
|
||||
return unserialize($userConfig, ['allowed_classes' => ['stdClass']]);
|
||||
}
|
||||
|
||||
private function getAffectedBackendUsers(): iterable
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder
|
||||
->select('uid', 'uc')
|
||||
->from('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->like(
|
||||
'uc',
|
||||
$queryBuilder->createNamedParameter(
|
||||
'%"stdClass"%'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $statement->execute();
|
||||
}
|
||||
|
||||
private function updateBackendUser(int $userId, array $userConfig): void
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users');
|
||||
$connection->update('be_users', ['uc' => serialize($userConfig)], ['uid' => $userId], [\PDO::PARAM_LOB, \PDO::PARAM_INT]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Update backend user setting startModule if set to "help_aboutmodules" or "help_CshmanualCshmanual"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class BackendUserStartModuleUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'cshmanualBackendUsers';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Update backend user setting "startModule"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The backend user setting startModule is changed for the extensions about/aboutmodules'
|
||||
. ' and help/cshmanual. Update all backend users that use EXT:aboutmodules and'
|
||||
. ' EXT:cshmanual as startModule.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$statement = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('be_users')
|
||||
->select(['uid', 'uc'], 'be_users', []);
|
||||
$needsExecution = false;
|
||||
while ($backendUser = $statement->fetch()) {
|
||||
if ($backendUser['uc'] !== null) {
|
||||
$userConfig = unserialize($backendUser['uc'], ['allowed_classes' => false]);
|
||||
if ($userConfig['startModule'] === 'help_aboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutmodulesAboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutAboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_CshmanualCshmanual'
|
||||
|| $userConfig['startModule'] === 'help_DocumentationCshmanual'
|
||||
) {
|
||||
$needsExecution = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $needsExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update if backend user's startmodule is
|
||||
* "help_aboutmodules" or "help_AboutmodulesAboutmodules" or "help_CshmanualCshmanual"
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$statement = $queryBuilder->select('uid', 'uc')->from('be_users')->execute();
|
||||
while ($backendUser = $statement->fetch()) {
|
||||
if ($backendUser['uc'] !== null) {
|
||||
$userConfig = unserialize($backendUser['uc'], ['allowed_classes' => false]);
|
||||
if ($userConfig['startModule'] === 'help_aboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutmodulesAboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutAboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_CshmanualCshmanual'
|
||||
|| $userConfig['startModule'] === 'help_DocumentationCshmanual'
|
||||
) {
|
||||
$userConfig['startModule'] = 'help_AboutAbout';
|
||||
if ($userConfig['startModule'] === 'help_CshmanualCshmanual' || $userConfig['startModule'] === 'help_DocumentationCshmanual') {
|
||||
$userConfig['startModule'] = 'help_BackendCshmanual';
|
||||
}
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->update('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($backendUser['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('uc', $queryBuilder->createNamedParameter(serialize($userConfig), \PDO::PARAM_LOB))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Migrate bullet content element rendering from layout to bullets_type
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class BulletContentElementUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'bulletContentElementUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate bullet content element rendering selector from layout to bullets_type';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Rendering type field has been streamlined with fluid_styled_content.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$elementCount = $queryBuilder->count('uid')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('bullets', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->in('layout', [1, 2])
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
return (bool)$elementCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'layout')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('bullets', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->in('layout', [1, 2])
|
||||
)
|
||||
->execute();
|
||||
while ($record = $statement->fetch()) {
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->update('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('layout', 0, false)
|
||||
->set('bullets_type', $record['layout']);
|
||||
$queryBuilder->execute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Is this upgradeWizard chatty aka does it need to output things?
|
||||
*/
|
||||
interface ChattyInterface
|
||||
{
|
||||
/**
|
||||
* Setter injection for output into upgrade wizards
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Remove all backend users starting with _cli_
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class CommandLineBackendUserRemovalUpdate implements UpgradeWizardInterface, ChattyInterface, RepeatableInterface, ConfirmableInterface
|
||||
{
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'The following backend users will be removed: ' . implode(', ', $this->getUnneededCommandLineUsers()),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'commandLineBackendUserRemovalUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Remove unneeded CLI backend users';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The command line interface does not need to have custom _cli_* backend users anymore.'
|
||||
. ' They can safely be deleted.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$needsExecution = false;
|
||||
$usersFound = $this->getUnneededCommandLineUsers();
|
||||
if (!empty($usersFound)) {
|
||||
$needsExecution = true;
|
||||
}
|
||||
return $needsExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update to set all be_users starting with _CLI_* to deleted
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$usersFound = $this->getUnneededCommandLineUsers();
|
||||
foreach ($usersFound as $userUid => $username) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->update('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($userUid, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
// "false" is set as third parameter to have the final
|
||||
// value in $databaseQueries and not a statement placeholder
|
||||
->set('deleted', 1, false)
|
||||
->execute();
|
||||
}
|
||||
$this->output->writeln('The following backend users have been deleted:');
|
||||
foreach ($usersFound as $user) {
|
||||
$this->output->writeln('* ' . $user);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all backend users starting with _CLI_ that are not deleted yet.
|
||||
*
|
||||
* @return array a list of uids
|
||||
*/
|
||||
protected function getUnneededCommandLineUsers(): array
|
||||
{
|
||||
$commandLineUsers = [];
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
|
||||
$result = $queryBuilder
|
||||
->select('uid', 'username')
|
||||
->from('be_users')
|
||||
->where(
|
||||
// Using query builder is complicated in this case. Get it straight, no user input is involved.
|
||||
'LOWER(username) LIKE \'_cli_%\'',
|
||||
$queryBuilder->expr()->neq(
|
||||
'username',
|
||||
$queryBuilder->createNamedParameter('_cli_', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$commandLineUsers[$row['uid']] = $row['username'];
|
||||
}
|
||||
|
||||
return $commandLineUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
|
||||
/**
|
||||
* Installs and downloads EXT:compatibility7 if needed
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class Compatibility7ExtractionUpdate extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Install extension "compatibility7" from TER';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extensionKey = 'compatibility7';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $extensionDetails = [
|
||||
'compatibility7' => [
|
||||
'title' => 'Compatibility Mode for TYPO3 v7',
|
||||
'description' => 'Provides an additional backwards-compatibility layer with legacy functionality for sites that haven\'t fully migrated to TYPO3 v8 yet.',
|
||||
'versionString' => '8.7.1',
|
||||
'composerName' => 'friendsoftypo3/compatibility7',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\ExtensionModel
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'compatibility7',
|
||||
'Compatibility Mode for TYPO3 v7',
|
||||
'8.7.1',
|
||||
'friendsoftypo3/compatibility7',
|
||||
'Provides an additional backwards-compatibility layer with legacy functionality for sites that haven\'t fully migrated to TYPO3 v8 yet.'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'The compatibility extensions come with a performance penalty, use only if needed. ' . $this->extension->getDescription(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'compatibility7Extension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install compatibility extension for TYPO3 7 compatibility';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The extension "compatibility7" (Compatibility Mode for TYPO3 v7) was extracted into '
|
||||
. 'the TYPO3 Extension Repository. This update downloads the TYPO3 Extension from the TER.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !ExtensionManagementUtility::isLoaded('compatibility7');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Use if upgrade wizard needs confirmation
|
||||
*/
|
||||
interface ConfirmableInterface
|
||||
{
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
class Confirmation
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $defaultValue = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $confirm;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $deny;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $required;
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
* @param string $message
|
||||
* @param bool $defaultValue
|
||||
* @param string $confirm
|
||||
* @param string $deny
|
||||
* @param bool $required
|
||||
*/
|
||||
public function __construct(
|
||||
string $title,
|
||||
string $message,
|
||||
bool $defaultValue = false,
|
||||
string $confirm = 'Yes, execute',
|
||||
string $deny = 'No, do not execute',
|
||||
bool $required = false
|
||||
) {
|
||||
$this->title = $title;
|
||||
$this->message = $message;
|
||||
$this->defaultValue = $defaultValue;
|
||||
$this->confirm = $confirm;
|
||||
$this->deny = $deny;
|
||||
$this->required = $required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getConfirm(): string
|
||||
{
|
||||
return $this->confirm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDeny(): string
|
||||
{
|
||||
return $this->deny;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getDefaultValue(): bool
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Updates\RowUpdater\ImageCropUpdater;
|
||||
use TYPO3\CMS\Install\Updates\RowUpdater\L10nModeUpdater;
|
||||
use TYPO3\CMS\Install\Updates\RowUpdater\RowUpdaterInterface;
|
||||
use TYPO3\CMS\Install\Updates\RowUpdater\RteLinkSyntaxUpdater;
|
||||
|
||||
/**
|
||||
* This is a generic updater to migrate content of TCA rows.
|
||||
*
|
||||
* Multiple classes implementing interface "RowUpdateInterface" can be
|
||||
* registered here, each for a specific update purpose.
|
||||
*
|
||||
* The updater fetches each row of all TCA registered tables and
|
||||
* visits the client classes who may modify the row content.
|
||||
*
|
||||
* The updater remembers for each class if it run through, so the updater
|
||||
* will be shown again if a new updater class is registered that has not
|
||||
* been run yet.
|
||||
*
|
||||
* A start position pointer is stored in the registry that is updated during
|
||||
* the run process, so if for instance the PHP process runs into a timeout,
|
||||
* the job can restart at the position it stopped.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class DatabaseRowsUpdateWizard implements UpgradeWizardInterface, RepeatableInterface
|
||||
{
|
||||
/**
|
||||
* @var array Single classes that may update rows
|
||||
*/
|
||||
protected $rowUpdater = [
|
||||
L10nModeUpdater::class,
|
||||
ImageCropUpdater::class,
|
||||
RteLinkSyntaxUpdater::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAvailableRowUpdater(): array
|
||||
{
|
||||
return $this->rowUpdater;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'databaseRowsUpdateWizard';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Execute database migrations on single rows';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
$rowUpdaterNotExecuted = $this->getRowUpdatersToExecute();
|
||||
$description = 'Row updaters that have not been executed:';
|
||||
foreach ($rowUpdaterNotExecuted as $rowUpdateClassName) {
|
||||
$rowUpdater = GeneralUtility::makeInstance($rowUpdateClassName);
|
||||
if (!$rowUpdater instanceof RowUpdaterInterface) {
|
||||
throw new \RuntimeException(
|
||||
'Row updater must implement RowUpdaterInterface',
|
||||
1484066647
|
||||
);
|
||||
}
|
||||
$description .= LF . $rowUpdater->getTitle();
|
||||
}
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if at least one row updater is not marked done
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !empty($this->getRowUpdatersToExecute());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the configuration update.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Doctrine\DBAL\ConnectionException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
|
||||
// If rows from the target table that is updated and the sys_registry table are on the
|
||||
// same connection, the row update statement and sys_registry position update will be
|
||||
// handled in a transaction to have an atomic operation in case of errors during execution.
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$connectionForSysRegistry = $connectionPool->getConnectionForTable('sys_registry');
|
||||
|
||||
/** @var RowUpdaterInterface[] $rowUpdaterInstances */
|
||||
$rowUpdaterInstances = [];
|
||||
// Single row updater instances are created only once for this method giving
|
||||
// them a chance to set up local properties during hasPotentialUpdateForTable()
|
||||
// and using that in updateTableRow()
|
||||
foreach ($this->getRowUpdatersToExecute() as $rowUpdater) {
|
||||
$rowUpdaterInstance = GeneralUtility::makeInstance($rowUpdater);
|
||||
if (!$rowUpdaterInstance instanceof RowUpdaterInterface) {
|
||||
throw new \RuntimeException(
|
||||
'Row updater must implement RowUpdaterInterface',
|
||||
1484071612
|
||||
);
|
||||
}
|
||||
$rowUpdaterInstances[] = $rowUpdaterInstance;
|
||||
}
|
||||
|
||||
// Scope of the row updater is to update all rows that have TCA,
|
||||
// our list of tables is just the list of loaded TCA tables.
|
||||
$listOfAllTables = array_keys($GLOBALS['TCA']);
|
||||
|
||||
// In case the PHP ended for whatever reason, fetch the last position from registry
|
||||
// and throw away all tables before that start point.
|
||||
sort($listOfAllTables);
|
||||
reset($listOfAllTables);
|
||||
$firstTable = current($listOfAllTables);
|
||||
$startPosition = $this->getStartPosition($firstTable);
|
||||
foreach ($listOfAllTables as $key => $table) {
|
||||
if ($table === $startPosition['table']) {
|
||||
break;
|
||||
}
|
||||
unset($listOfAllTables[$key]);
|
||||
}
|
||||
|
||||
// Ask each row updater if it potentially has field updates for rows of a table
|
||||
$tableToUpdaterList = [];
|
||||
foreach ($listOfAllTables as $table) {
|
||||
foreach ($rowUpdaterInstances as $updater) {
|
||||
if ($updater->hasPotentialUpdateForTable($table)) {
|
||||
if (!is_array($tableToUpdaterList[$table])) {
|
||||
$tableToUpdaterList[$table] = [];
|
||||
}
|
||||
$tableToUpdaterList[$table][] = $updater;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through all rows of all tables that have potential row updaters attached,
|
||||
// feed each single row to each updater and finally update each row in database if
|
||||
// a row updater changed a fields
|
||||
foreach ($tableToUpdaterList as $table => $updaters) {
|
||||
/** @var RowUpdaterInterface[] $updaters */
|
||||
$connectionForTable = $connectionPool->getConnectionForTable($table);
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->select('*')
|
||||
->from($table)
|
||||
->orderBy('uid');
|
||||
if ($table === $startPosition['table']) {
|
||||
$queryBuilder->where(
|
||||
$queryBuilder->expr()->gt('uid', $queryBuilder->createNamedParameter($startPosition['uid']))
|
||||
);
|
||||
}
|
||||
$statement = $queryBuilder->execute();
|
||||
$rowCountWithoutUpdate = 0;
|
||||
while ($row = $rowBefore = $statement->fetch()) {
|
||||
foreach ($updaters as $updater) {
|
||||
$row = $updater->updateTableRow($table, $row);
|
||||
}
|
||||
$updatedFields = array_diff_assoc($row, $rowBefore);
|
||||
if (empty($updatedFields)) {
|
||||
// Updaters changed no field of that row
|
||||
$rowCountWithoutUpdate++;
|
||||
if ($rowCountWithoutUpdate >= 200) {
|
||||
// Update startPosition if there were many rows without data change
|
||||
$startPosition = [
|
||||
'table' => $table,
|
||||
'uid' => $row['uid'],
|
||||
];
|
||||
$registry->set('installUpdateRows', 'rowUpdatePosition', $startPosition);
|
||||
$rowCountWithoutUpdate = 0;
|
||||
}
|
||||
} else {
|
||||
$rowCountWithoutUpdate = 0;
|
||||
$startPosition = [
|
||||
'table' => $table,
|
||||
'uid' => $rowBefore['uid'],
|
||||
];
|
||||
if ($connectionForSysRegistry === $connectionForTable) {
|
||||
// Target table and sys_registry table are on the same connection, use a transaction
|
||||
$connectionForTable->beginTransaction();
|
||||
try {
|
||||
$connectionForTable->update(
|
||||
$table,
|
||||
$updatedFields,
|
||||
[
|
||||
'uid' => $rowBefore['uid'],
|
||||
]
|
||||
);
|
||||
$connectionForTable->update(
|
||||
'sys_registry',
|
||||
[
|
||||
'entry_value' => serialize($startPosition),
|
||||
],
|
||||
[
|
||||
'entry_namespace' => 'installUpdateRows',
|
||||
'entry_key' => 'rowUpdatePosition',
|
||||
]
|
||||
);
|
||||
$connectionForTable->commit();
|
||||
} catch (\Exception $up) {
|
||||
$connectionForTable->rollBack();
|
||||
throw $up;
|
||||
}
|
||||
} else {
|
||||
// Different connections for table and sys_registry -> execute two
|
||||
// distinct queries and hope for the best.
|
||||
$connectionForTable->update(
|
||||
$table,
|
||||
$updatedFields,
|
||||
[
|
||||
'uid' => $rowBefore['uid'],
|
||||
]
|
||||
);
|
||||
$connectionForSysRegistry->update(
|
||||
'sys_registry',
|
||||
[
|
||||
'entry_value' => serialize($startPosition),
|
||||
],
|
||||
[
|
||||
'entry_namespace' => 'installUpdateRows',
|
||||
'entry_key' => 'rowUpdatePosition',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ready with updates, remove position information from sys_registry
|
||||
$registry->remove('installUpdateRows', 'rowUpdatePosition');
|
||||
// Mark row updaters that were executed as done
|
||||
foreach ($rowUpdaterInstances as $updater) {
|
||||
$this->setRowUpdaterExecuted($updater);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of class names that are not yet marked as done.
|
||||
*
|
||||
* @return array Class names
|
||||
*/
|
||||
protected function getRowUpdatersToExecute(): array
|
||||
{
|
||||
$doneRowUpdater = GeneralUtility::makeInstance(Registry::class)->get('installUpdateRows', 'rowUpdatersDone', []);
|
||||
return array_diff($this->rowUpdater, $doneRowUpdater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a single updater as done
|
||||
*
|
||||
* @param RowUpdaterInterface $updater
|
||||
*/
|
||||
protected function setRowUpdaterExecuted(RowUpdaterInterface $updater)
|
||||
{
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$doneRowUpdater = $registry->get('installUpdateRows', 'rowUpdatersDone', []);
|
||||
$doneRowUpdater[] = get_class($updater);
|
||||
$registry->set('installUpdateRows', 'rowUpdatersDone', $doneRowUpdater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with table / uid combination that specifies the start position the
|
||||
* update row process should start with.
|
||||
*
|
||||
* @param string $firstTable Table name of the first TCA in case the start position needs to be initialized
|
||||
* @return array New start position
|
||||
*/
|
||||
protected function getStartPosition(string $firstTable): array
|
||||
{
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$startPosition = $registry->get('installUpdateRows', 'rowUpdatePosition', []);
|
||||
if (empty($startPosition)) {
|
||||
$startPosition = [
|
||||
'table' => $firstTable,
|
||||
'uid' => 0,
|
||||
];
|
||||
$registry->set('installUpdateRows', 'rowUpdatePosition', $startPosition);
|
||||
}
|
||||
return $startPosition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Install\Service\UpgradeWizardsService;
|
||||
|
||||
/**
|
||||
* Prerequisite for upgrade wizards to ensure the database is up-to-date
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DatabaseUpdatedPrerequisite implements PrerequisiteInterface, ChattyInterface
|
||||
{
|
||||
/**
|
||||
* @var UpgradeWizardsService
|
||||
*/
|
||||
protected $upgradeWizardsService;
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->upgradeWizardsService = new UpgradeWizardsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Database Up-to-Date';
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public function ensure(): bool
|
||||
{
|
||||
$adds = $this->upgradeWizardsService->getBlockingDatabaseAdds();
|
||||
$result = null;
|
||||
if (count($adds) > 0) {
|
||||
$this->output->writeln('Performing ' . count($adds) . ' database operations.');
|
||||
$result = $this->upgradeWizardsService->addMissingTablesAndFields();
|
||||
}
|
||||
return $result === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFulfilled(): bool
|
||||
{
|
||||
$adds = $this->upgradeWizardsService->getBlockingDatabaseAdds();
|
||||
return count($adds) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
|
||||
use TYPO3\CMS\Core\Database\Schema\SqlReader;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Contains the update class for filling the basic repository record of the extension manager
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ExtensionManagerTables implements UpgradeWizardInterface, RepeatableInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'extensionManagerTables';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Add the default Extension Manager database tables';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Creates necessary database tables and adds static data for the Extension Manager.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$result = false;
|
||||
// First check necessary database update
|
||||
$updateStatements = array_filter($this->getUpdateStatements());
|
||||
if (count($updateStatements) === 0) {
|
||||
// Get count of rows in repository database table
|
||||
$count = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tx_extensionmanager_domain_model_repository')
|
||||
->count('*', 'tx_extensionmanager_domain_model_repository', []);
|
||||
|
||||
if ($count === 0) {
|
||||
$result = true;
|
||||
}
|
||||
} else {
|
||||
$result = true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update.
|
||||
*
|
||||
* @return bool Whether it worked (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
$sqlReader = GeneralUtility::makeInstance(SqlReader::class);
|
||||
$createTableStatements = $this->getTableStatements();
|
||||
|
||||
// First perform all create, add and change queries
|
||||
$schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
|
||||
$schemaMigrationService->install($createTableStatements);
|
||||
|
||||
// Perform import of static data
|
||||
$rawDefinitions = file_get_contents(
|
||||
ExtensionManagementUtility::extPath('extensionmanager', 'ext_tables_static+adt.sql')
|
||||
);
|
||||
|
||||
$insertStatements = $sqlReader->getInsertStatementArray($rawDefinitions);
|
||||
$schemaMigrationService->importStaticData($insertStatements);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all create, add and change queries from ext_tables.sql
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUpdateStatements()
|
||||
{
|
||||
$updateStatements = [];
|
||||
$emTableStatements = $this->getTableStatements();
|
||||
if (count($emTableStatements)) {
|
||||
$schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
|
||||
$updateSuggestions = $schemaMigrationService->getUpdateSuggestions($emTableStatements);
|
||||
$updateStatements = array_merge_recursive(...array_values($updateSuggestions));
|
||||
}
|
||||
return $updateStatements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all CREATE TABLE statements from the ext_tables.sql file
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getTableStatements(): array
|
||||
{
|
||||
$rawDefinitions = file_get_contents(ExtensionManagementUtility::extPath('extensionmanager', 'ext_tables.sql'));
|
||||
$sqlReader = GeneralUtility::makeInstance(SqlReader::class);
|
||||
return $sqlReader->getCreateTableStatementArray($rawDefinitions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Model for extensions installed by upgrade wizards
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ExtensionModel
|
||||
{
|
||||
protected $key = '';
|
||||
protected $title = '';
|
||||
protected $versionString = '';
|
||||
protected $composerName = '';
|
||||
protected $description = '';
|
||||
|
||||
public function __construct(
|
||||
string $key,
|
||||
string $title,
|
||||
string $versionString,
|
||||
string $composerName,
|
||||
string $description
|
||||
) {
|
||||
$this->key = $key;
|
||||
$this->title = $title;
|
||||
$this->versionString = $versionString;
|
||||
$this->composerName = $composerName;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getVersionString(): string
|
||||
{
|
||||
return $this->versionString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComposerName(): string
|
||||
{
|
||||
return $this->composerName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Resource\Exception;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Migrate file references that are stored in a wrong way to correct scheme
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FileReferenceUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'fileReferenceUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate file references that are stored in a wrong way to correct scheme';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'File references were saved in a wrong way and references are not shown correctly in file list module.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if there are records to update
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
|
||||
return (bool)$queryBuilder->count('hash')
|
||||
->from('sys_refindex')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter('_FILE', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->eq('softref_key', $queryBuilder->createNamedParameter('typolink_tag', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_refindex');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$statement = $queryBuilder->select('*')
|
||||
->from('sys_refindex')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter('_FILE', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->eq('softref_key', $queryBuilder->createNamedParameter('typolink_tag', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute();
|
||||
while ($record = $statement->fetch()) {
|
||||
$fileReference = 0;
|
||||
if (MathUtility::canBeInterpretedAsInteger($record['ref_string'])) {
|
||||
$fileReference = $record['ref_string'];
|
||||
} else {
|
||||
try {
|
||||
$fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($record['ref_string']);
|
||||
if ($fileObject instanceof File) {
|
||||
$fileReference = $fileObject->getUid();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Silently catch if there is no file object
|
||||
}
|
||||
}
|
||||
|
||||
$updateQueryBuilder = $connection->createQueryBuilder();
|
||||
$updateQueryBuilder->update('sys_refindex')
|
||||
->where(
|
||||
$updateQueryBuilder->expr()->eq(
|
||||
'hash',
|
||||
$updateQueryBuilder->createNamedParameter($record['hash'], \PDO::PARAM_STR)
|
||||
)
|
||||
);
|
||||
|
||||
if ($fileReference) {
|
||||
$updateQueryBuilder->set('ref_table', 'sys_file')
|
||||
->set('ref_uid', $fileReference)
|
||||
->set('ref_string', '');
|
||||
} else {
|
||||
$updateQueryBuilder->set('deleted', 1);
|
||||
}
|
||||
|
||||
$updateQueryBuilder->execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Fill translation source field (l10n_source)
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FillTranslationSourceField implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'fillTranslationSourceField';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Fill translation source field (l10n_source)';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Fill translation source field (l10n_source) for tt_contents which have l18n_parent set.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tt_content');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$query = $queryBuilder->count('uid')
|
||||
->from('tt_content')
|
||||
->where($queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->gt('l18n_parent', $queryBuilder->createNamedParameter(0)),
|
||||
$queryBuilder->expr()->eq('l10n_source', $queryBuilder->createNamedParameter(0))
|
||||
));
|
||||
return (bool)$query->execute()->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tt_content');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->update('tt_content', 't')
|
||||
->set('t.l10n_source', 't.l18n_parent', false)
|
||||
->where($queryBuilder->expr()->andX(
|
||||
$queryBuilder->expr()->gt('t.l18n_parent', $queryBuilder->createNamedParameter(0)),
|
||||
$queryBuilder->expr()->eq('t.l10n_source', $queryBuilder->createNamedParameter(0))
|
||||
))
|
||||
->execute();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Installs and downloads EXT:form_legacy if needed
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FormLegacyExtractionUpdate extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'form_legacy',
|
||||
'Legacy form extension for TYPO3 v7 compatibility',
|
||||
'8.7.0',
|
||||
'friendsoftypo3/form-legacy',
|
||||
'Provides an additional backwards-compatibility layer with legacy functionality for sites that used the form extension in TYPO3 v7.'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you really sure, you want to install EXT:form_legacy?',
|
||||
'You should install EXT:form_legacy only if you really need it.'
|
||||
. 'This update wizard checked all content elements and found at least one not deleted element based'
|
||||
. 'on the old form module. It is advised to manually convert those elements from the old form implementation'
|
||||
. 'to the new implementation of EXT:form. EXT:form_legacy should be unloaded and removed afterwards.',
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'formLegacyExtractionUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install extension "form_legacy"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The extension "form" was rewritten in TYPO3 v8 and follows a new approach.'
|
||||
. 'This update downloads the old implementation of the form extension as known from TYPO3 v7 from the TER.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$updateNeeded = false;
|
||||
|
||||
if (!ExtensionManagementUtility::isLoaded('form_legacy')) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$count = $queryBuilder
|
||||
->count('*')
|
||||
->from('tt_content')
|
||||
->where($queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('mailform')))
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
if ($count > 0) {
|
||||
$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 [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/**
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
||||
use TYPO3\CMS\Core\Resource\StorageRepository;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
|
||||
/**
|
||||
* Upgrade wizard which goes through all files referenced in fe_users::image
|
||||
* and creates sys_file records as well as sys_file_reference records for each hit.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FrontendUserImageUpdateWizard implements UpgradeWizardInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* Number of records fetched per database query
|
||||
* Used to prevent memory overflows for huge databases
|
||||
*/
|
||||
const RECORDS_PER_QUERY = 1000;
|
||||
|
||||
/**
|
||||
* @var ResourceStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Table to migrate records from
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'fe_users';
|
||||
|
||||
/**
|
||||
* Table field holding the migration to be
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldToMigrate = 'image';
|
||||
|
||||
/**
|
||||
* the source file resides here
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sourcePath = 'uploads/pics/';
|
||||
|
||||
/**
|
||||
* target folder after migration
|
||||
* Relative to fileadmin
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $targetPath = '_migrated/frontend_users/';
|
||||
|
||||
/**
|
||||
* @var Registry
|
||||
*/
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $registryNamespace = 'FrontendUserImageUpdateWizard';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $recordOffset = [];
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'frontendUserImageUpdateWizard';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate all file relations from fe_users.image to sys_file_references';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'This update wizard goes through all files that are referenced in the fe_users.image'
|
||||
. ' field and adds the files to the FAL File Index. It also moves the files from'
|
||||
. ' uploads/ to the fileadmin/_migrated/ path.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool TRUE if an update is needed, FALSE otherwise
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$this->registry = GeneralUtility::makeInstance(Registry::class);
|
||||
return $this->registry->get($this->registryNamespace, 'recordOffset') === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update.
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on error
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
try {
|
||||
$this->init();
|
||||
if (!isset($this->recordOffset[$this->table])) {
|
||||
$this->recordOffset[$this->table] = 0;
|
||||
}
|
||||
do {
|
||||
$limit = $this->recordOffset[$this->table] . ',' . self::RECORDS_PER_QUERY;
|
||||
$records = $this->getRecordsFromTable($limit);
|
||||
foreach ($records as $record) {
|
||||
$this->migrateField($record);
|
||||
}
|
||||
$this->registry->set($this->registryNamespace, 'recordOffset', $this->recordOffset);
|
||||
} while (count($records) === self::RECORDS_PER_QUERY);
|
||||
|
||||
$this->registry->remove($this->registryNamespace, 'recordOffset');
|
||||
} catch (\Exception $e) {
|
||||
// Silently catch db errors
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the storage repository.
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
$storages = GeneralUtility::makeInstance(StorageRepository::class)->findAll();
|
||||
$this->storage = $storages[0];
|
||||
$this->registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$this->recordOffset = $this->registry->get($this->registryNamespace, 'recordOffset', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get records from table where the field to migrate is not empty (NOT NULL and != '')
|
||||
* and also not numeric (which means that it is migrated)
|
||||
*
|
||||
* @param int $limit Maximum number records to select
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getRecordsFromTable($limit)
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
try {
|
||||
return $queryBuilder
|
||||
->select('uid', 'pid', $this->fieldToMigrate)
|
||||
->from($this->table)
|
||||
->where(
|
||||
$queryBuilder->expr()->isNotNull($this->fieldToMigrate),
|
||||
$queryBuilder->expr()->neq(
|
||||
$this->fieldToMigrate,
|
||||
$queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->comparison(
|
||||
'CAST(CAST(' . $queryBuilder->quoteIdentifier($this->fieldToMigrate) . ' AS DECIMAL) AS CHAR)',
|
||||
ExpressionBuilder::NEQ,
|
||||
'CAST(' . $queryBuilder->quoteIdentifier($this->fieldToMigrate) . ' AS CHAR)'
|
||||
)
|
||||
)
|
||||
->orderBy('uid')
|
||||
->setFirstResult($limit)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
} catch (DBALException $e) {
|
||||
throw new \RuntimeException(
|
||||
'Database query failed. Error was: ' . $e->getPrevious()->getMessage(),
|
||||
1476050084
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates a single field.
|
||||
*
|
||||
* @param array $row
|
||||
*/
|
||||
protected function migrateField($row)
|
||||
{
|
||||
$fieldItems = GeneralUtility::trimExplode(',', $row[$this->fieldToMigrate], true);
|
||||
if (empty($fieldItems) || is_numeric($row[$this->fieldToMigrate])) {
|
||||
return;
|
||||
}
|
||||
$fileadminDirectory = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/';
|
||||
$i = 0;
|
||||
|
||||
$storageUid = (int)$this->storage->getUid();
|
||||
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
|
||||
foreach ($fieldItems as $item) {
|
||||
$fileUid = null;
|
||||
$sourcePath = Environment::getPublicPath() . '/' . $this->sourcePath . $item;
|
||||
$targetDirectory = Environment::getPublicPath() . '/' . $fileadminDirectory . $this->targetPath;
|
||||
$targetPath = $targetDirectory . PathUtility::basename($item);
|
||||
|
||||
// maybe the file was already moved, so check if the original file still exists
|
||||
if (file_exists($sourcePath)) {
|
||||
if (!is_dir($targetDirectory)) {
|
||||
GeneralUtility::mkdir_deep($targetDirectory);
|
||||
}
|
||||
|
||||
// see if the file already exists in the storage
|
||||
$fileSha1 = sha1_file($sourcePath);
|
||||
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$existingFileRecord = $queryBuilder->select('uid')->from('sys_file')->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'sha1',
|
||||
$queryBuilder->createNamedParameter($fileSha1, \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'storage',
|
||||
$queryBuilder->createNamedParameter($storageUid, \PDO::PARAM_INT)
|
||||
)
|
||||
)->execute()->fetch();
|
||||
|
||||
// the file exists, the file does not have to be moved again
|
||||
if (is_array($existingFileRecord)) {
|
||||
$fileUid = $existingFileRecord['uid'];
|
||||
} else {
|
||||
// just move the file (no duplicate)
|
||||
rename($sourcePath, $targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
if ($fileUid === null) {
|
||||
// get the File object if it hasn't been fetched before
|
||||
try {
|
||||
// if the source file does not exist, we should just continue, but leave a message in the docs;
|
||||
// ideally, the user would be informed after the update as well.
|
||||
/** @var File $file */
|
||||
$file = $this->storage->getFile($this->targetPath . $item);
|
||||
$fileUid = $file->getUid();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// no file found, no reference can be set
|
||||
$this->logger->notice(
|
||||
'File ' . $this->sourcePath . $item . ' does not exist. Reference was not migrated.',
|
||||
[
|
||||
'table' => $this->table,
|
||||
'record' => $row,
|
||||
'field' => $this->fieldToMigrate,
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fileUid > 0) {
|
||||
$fields = [
|
||||
'fieldname' => $this->fieldToMigrate,
|
||||
'table_local' => 'sys_file',
|
||||
'pid' => $this->table === 'pages' ? $row['uid'] : $row['pid'],
|
||||
'uid_foreign' => $row['uid'],
|
||||
'uid_local' => $fileUid,
|
||||
'tablenames' => $this->table,
|
||||
'crdate' => time(),
|
||||
'tstamp' => time(),
|
||||
'sorting_foreign' => $i,
|
||||
];
|
||||
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
|
||||
$queryBuilder->insert('sys_file_reference')->values($fields)->execute();
|
||||
++$i;
|
||||
}
|
||||
}
|
||||
|
||||
// Update referencing table's original field to now contain the count of references,
|
||||
// but only if all new references could be set
|
||||
if ($i === count($fieldItems)) {
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
|
||||
$queryBuilder->update($this->table)->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)->set($this->fieldToMigrate, $i)->execute();
|
||||
} else {
|
||||
$this->recordOffset[$this->table]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
|
||||
/**
|
||||
* Installs and downloads EXT:func
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FuncExtractionUpdate extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'func',
|
||||
'Web->Functions module',
|
||||
'9.0.1',
|
||||
'friendsoftypo3/cms-func',
|
||||
'Provides Web->Functions BE module used in previous TYPO3 versions for extensions that still rely on it.'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'You should install EXT:func only if you really need it. ' . $this->extension->getDescription(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'funcExtension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install extension "func" from TER';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The extension "func" that brings the "Web->Functions" backend module has been extracted to'
|
||||
. ' the TYPO3 Extension Repository. This update downloads the TYPO3 extension func from the TER.'
|
||||
. ' Use this if you\'re dealing with extensions in the instance that rely on "Web->Functions" and bring own'
|
||||
. ' modules.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !ExtensionManagementUtility::isLoaded($this->extension->getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Update sys_language records to use the newly sorting column,
|
||||
* set default sorting from title
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class LanguageSortingUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'sysLanguageSorting';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Update sorting of sys_language records';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The sys_language records have unsorted rows. '
|
||||
. ' This upgrade wizard adds values depending on the language title';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_language');
|
||||
return (bool)$queryBuilder->count('uid')
|
||||
->from('sys_language')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('sorting', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->isNotNull('sorting')
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update if the sorting field is 0 or null
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_language');
|
||||
$statement = $queryBuilder->select('uid')
|
||||
->from('sys_language')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('sorting', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
)
|
||||
->orderBy('title')
|
||||
->execute();
|
||||
$sortCounter = 10;
|
||||
while ($languageRecord = $statement->fetch()) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_language');
|
||||
$queryBuilder->update('sys_language')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($languageRecord['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('sorting', $sortCounter)
|
||||
->execute();
|
||||
$sortCounter += 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Merge sessions from old fe_session_data table into new structure from fe_sessions
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MigrateFeSessionDataUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'migrateFeSessionDataUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrates existing fe_session_data into fe_sessions';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'With the new Session Framwework the session data is stored in fe_sessions.'
|
||||
. ' To avoid that data is truncated, ensure the columns of fe_sessions have been updated.'
|
||||
. ' This wizard migrates the existing data from fe_session_data into fe_sessions.'
|
||||
. ' Existing entries in fe_sessions having an entry in fe_session_data are updated.'
|
||||
. ' Entries in fe_session_data not found in fe_sessions are inserted with ses_anonymous = true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (true) or not (false)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
if (!$this->checkIfTableExists('fe_session_data')) {
|
||||
return false;
|
||||
}
|
||||
// Check if there is data to migrate
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('fe_session_data');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$count = $queryBuilder->count('*')
|
||||
->from('fe_session_data')
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
|
||||
return $count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves data from fe_session_data into fe_sessions with respect to ses_anonymous
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('fe_sessions');
|
||||
|
||||
// Process records that have entries in fe_sessions and fe_session_data
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$statement = $queryBuilder->select('fe_session_data.hash', 'fe_session_data.content')
|
||||
->from('fe_sessions')
|
||||
->join(
|
||||
'fe_sessions',
|
||||
'fe_session_data',
|
||||
'fe_session_data',
|
||||
$queryBuilder->expr()->eq(
|
||||
'fe_sessions.ses_id',
|
||||
$queryBuilder->quoteIdentifier('fe_session_data.hash')
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
|
||||
$updateQueryBuilder = $connection->createQueryBuilder();
|
||||
$updateQueryBuilder->update('fe_sessions')
|
||||
->where(
|
||||
$updateQueryBuilder->expr()->eq(
|
||||
'ses_id',
|
||||
$updateQueryBuilder->createPositionalParameter('', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
->set('ses_data', $updateQueryBuilder->createPositionalParameter('', \PDO::PARAM_STR), false);
|
||||
$updateStatement = $connection->prepare($updateQueryBuilder->getSQL());
|
||||
|
||||
$connection->beginTransaction();
|
||||
try {
|
||||
while ($row = $statement->fetch()) {
|
||||
$updateStatement->execute([$row['hash'], $row['content']]);
|
||||
}
|
||||
$connection->commit();
|
||||
} catch (DBALException $e) {
|
||||
$connection->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Move records from fe_session_data that are not in fe_sessions
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$selectSQL = $queryBuilder->select('fe_session_data.hash', 'fe_session_data.content', 'fe_session_data.tstamp')
|
||||
->addSelectLiteral('1')
|
||||
->from('fe_session_data')
|
||||
->leftJoin(
|
||||
'fe_session_data',
|
||||
'fe_sessions',
|
||||
'fe_sessions',
|
||||
$queryBuilder->expr()->eq(
|
||||
'fe_session_data.hash',
|
||||
$queryBuilder->quoteIdentifier('fe_sessions.ses_id')
|
||||
)
|
||||
)
|
||||
->where($queryBuilder->expr()->isNull('fe_sessions.ses_id'))
|
||||
->getSQL();
|
||||
|
||||
$insertSQL = sprintf(
|
||||
'INSERT INTO %s(%s, %s, %s, %s) %s',
|
||||
$connection->quoteIdentifier('fe_sessions'),
|
||||
$connection->quoteIdentifier('ses_id'),
|
||||
$connection->quoteIdentifier('ses_data'),
|
||||
$connection->quoteIdentifier('ses_tstamp'),
|
||||
$connection->quoteIdentifier('ses_anonymous'),
|
||||
$selectSQL
|
||||
);
|
||||
|
||||
try {
|
||||
$connection->beginTransaction();
|
||||
$connection->exec($insertSQL);
|
||||
$connection->commit();
|
||||
} catch (DBALException $e) {
|
||||
$connection->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given table exists
|
||||
*
|
||||
* @param string $table
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkIfTableExists($table): bool
|
||||
{
|
||||
$tableExists = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable($table)
|
||||
->getSchemaManager()
|
||||
->tablesExist([$table]);
|
||||
|
||||
return $tableExists;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Migrate "fluid_styled_content" static template location
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MigrateFscStaticTemplateUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'migrateFscStaticTemplateUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate "fluid_styled_content" static template location';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Static templates have been relocated to EXT:fluid_styled_content/Configuration/TypoScript/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_template');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$elementCount = $queryBuilder->count('uid')
|
||||
->from('sys_template')
|
||||
->where(
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->like(
|
||||
'constants',
|
||||
$queryBuilder->createNamedParameter('%EXT:fluid_styled_content/Configuration/TypoScript/Static%', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->like(
|
||||
'config',
|
||||
$queryBuilder->createNamedParameter('%EXT:fluid_styled_content/Configuration/TypoScript/Static%', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->like(
|
||||
'include_static_file',
|
||||
$queryBuilder->createNamedParameter('%EXT:fluid_styled_content/Configuration/TypoScript/Static%', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
)
|
||||
->execute()->fetchColumn(0);
|
||||
return (bool)$elementCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'include_static_file', 'constants', 'config')
|
||||
->from('sys_template')
|
||||
->where(
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->like(
|
||||
'constants',
|
||||
$queryBuilder->createNamedParameter('%EXT:fluid_styled_content/Configuration/TypoScript/Static%', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->like(
|
||||
'config',
|
||||
$queryBuilder->createNamedParameter('%EXT:fluid_styled_content/Configuration/TypoScript/Static%', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->like(
|
||||
'include_static_file',
|
||||
$queryBuilder->createNamedParameter('%EXT:fluid_styled_content/Configuration/TypoScript/Static%', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
while ($record = $statement->fetch()) {
|
||||
$search = 'EXT:fluid_styled_content/Configuration/TypoScript/Static';
|
||||
$replace = 'EXT:fluid_styled_content/Configuration/TypoScript';
|
||||
$record['include_static_file'] = str_replace($search, $replace, $record['include_static_file']);
|
||||
$record['constants'] = str_replace($search, $replace, $record['constants']);
|
||||
$record['config'] = str_replace($search, $replace, $record['config']);
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->update('sys_template')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('include_static_file', $record['include_static_file'])
|
||||
->set('constants', $record['constants'])
|
||||
->set('config', $record['config']);
|
||||
$queryBuilder->execute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\UpgradeWizardsService;
|
||||
|
||||
/**
|
||||
* Merge access rights from be_groups concerning pages_language_overlay
|
||||
* into pages
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MigratePagesLanguageOverlayBeGroupsAccessRights implements UpgradeWizardInterface, ConfirmableInterface
|
||||
{
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'pagesLanguageOverlayBeGroupsAccessRights';
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Merge be_groups access rights from pages_language_overlay to pages';
|
||||
}
|
||||
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$beGroupsQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
|
||||
'be_groups'
|
||||
);
|
||||
$beGroupsQueryBuilder->getRestrictions()->removeAll();
|
||||
$beGroupsRows = $beGroupsQueryBuilder
|
||||
->select('uid', 'non_exclude_fields', 'tables_modify')
|
||||
->from('be_groups')
|
||||
->execute();
|
||||
while ($beGroupsRow = $beGroupsRows->fetch()) {
|
||||
$updateNeeded = false;
|
||||
if (!empty($beGroupsRow['tables_modify'])) {
|
||||
// If 'pages_language_overlay' is allowed as table-modify, remove it and add
|
||||
// 'pages' if it is not in there, yet.
|
||||
$tablesArray = GeneralUtility::trimExplode(',', $beGroupsRow['tables_modify'], true);
|
||||
$newTablesArray = $tablesArray;
|
||||
if (in_array('pages_language_overlay', $tablesArray, true)) {
|
||||
$updateNeeded = true;
|
||||
$newTablesArray = array_diff($tablesArray, ['pages_language_overlay']);
|
||||
if (!in_array('pages', $newTablesArray, true)) {
|
||||
$newTablesArray[] = 'pages';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$newTablesArray = [];
|
||||
}
|
||||
if (!empty($beGroupsRow['non_exclude_fields'])) {
|
||||
// Exclude fields on 'pages_language_overlay' are removed and added as
|
||||
// exclude fields on 'pages'
|
||||
$excludeFields = GeneralUtility::trimExplode(',', $beGroupsRow['non_exclude_fields'], true);
|
||||
$newExcludeFields = [];
|
||||
foreach ($excludeFields as $tableFieldCombo) {
|
||||
if (strpos($tableFieldCombo, 'pages_language_overlay:') === 0) {
|
||||
$updateNeeded = true;
|
||||
$field = substr($tableFieldCombo, strlen('pages_language_overlay:'));
|
||||
$newExcludeFields[] = 'pages:' . $field;
|
||||
} else {
|
||||
$newExcludeFields[] = $tableFieldCombo;
|
||||
}
|
||||
}
|
||||
array_unique($newExcludeFields);
|
||||
} else {
|
||||
$newExcludeFields = [];
|
||||
}
|
||||
if ($updateNeeded) {
|
||||
$updateBeGroupsQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('be_groups');
|
||||
$updateBeGroupsQueryBuilder
|
||||
->update('be_groups')
|
||||
->set('tables_modify', implode(',', $newTablesArray))
|
||||
->set('non_exclude_fields', implode(',', $newExcludeFields))
|
||||
->where(
|
||||
$updateBeGroupsQueryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$updateBeGroupsQueryBuilder->createNamedParameter($beGroupsRow['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !(new UpgradeWizardsService())->isWizardDone($this->getIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The table pages_language_overlay will be removed to align the translation ' .
|
||||
'handling for pages with the rest of the core. This wizard transfers all be_groups with ' .
|
||||
'access restrictions to pages_language_overlay into pages.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return GeneralUtility::makeInstance(
|
||||
Confirmation::class,
|
||||
'Are you sure?',
|
||||
'Do you want to continue?',
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Service\LoadTcaService;
|
||||
|
||||
/**
|
||||
* Merge pages_language_overlay rows into pages table
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MigratePagesLanguageOverlayUpdate implements UpgradeWizardInterface, ChattyInterface
|
||||
{
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'pagesLanguageOverlay';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate content from pages_language_overlay to pages';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The table pages_language_overlay will be removed to align the translation '
|
||||
. 'handling for pages with the rest of the core. This wizard transfers all data to the pages '
|
||||
. 'table by creating new entries and linking them to the l10n parent. This might take a while, '
|
||||
. 'because max. (amount of pages) x (active languages) new entries need be created.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether updates are required.
|
||||
*
|
||||
* @return bool Whether an update is required (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
// Check if the database table even exists
|
||||
if ($this->checkIfWizardIsRequired()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional output if there are columns with mm config
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the update.
|
||||
*
|
||||
* @return bool Whether everything went smoothly or not
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
// Warn for TCA relation configurations which are not migrated.
|
||||
if (isset($GLOBALS['TCA']['pages_language_overlay']['columns'])
|
||||
&& is_array($GLOBALS['TCA']['pages_language_overlay']['columns'])
|
||||
) {
|
||||
foreach ($GLOBALS['TCA']['pages_language_overlay']['columns'] as $fieldName => $fieldConfiguration) {
|
||||
if (isset($fieldConfiguration['config']['MM'])) {
|
||||
$this->output->writeln('The pages_language_overlay field ' . $fieldName
|
||||
. ' with its MM relation configuration can not be migrated'
|
||||
. ' automatically. Existing data relations to this field have'
|
||||
. ' to be migrated manually.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure pages_language_overlay is still available in TCA
|
||||
GeneralUtility::makeInstance(LoadTcaService::class)->loadExtensionTablesWithoutMigration();
|
||||
$this->mergePagesLanguageOverlayIntoPages();
|
||||
$this->updateInlineRelations();
|
||||
$this->updateSysHistoryRelations();
|
||||
$this->enableFeatureFlag();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Fetches ALL pages_language_overlay (= translations) records
|
||||
* 2. Fetches the given page record (= original language) for each translation
|
||||
* 3. Populates the values from the original language IF the field in the translation record is NOT SET (empty is fine)
|
||||
* 4. Adds proper fields for the translations which is
|
||||
* - l10n_parent = UID of the original-language-record
|
||||
* - pid = PID of the original-language-record (please note: THIS IS DIFFERENT THAN IN pages_language_overlay)
|
||||
* - l10n_source = UID of the original-language-record (only this is supported currently)
|
||||
*/
|
||||
protected function mergePagesLanguageOverlayIntoPages()
|
||||
{
|
||||
$overlayQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages_language_overlay');
|
||||
$overlayQueryBuilder->getRestrictions()->removeAll();
|
||||
$overlayRecords = $overlayQueryBuilder
|
||||
->select('*')
|
||||
->from('pages_language_overlay')
|
||||
->execute();
|
||||
$pagesConnection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
|
||||
$pagesColumns = $pagesConnection->getSchemaManager()->listTableDetails('pages')->getColumns();
|
||||
$pagesColumnTypes = [];
|
||||
foreach ($pagesColumns as $pageColumn) {
|
||||
$pagesColumnTypes[$pageColumn->getName()] = $pageColumn->getType()->getBindingType();
|
||||
}
|
||||
while ($overlayRecord = $overlayRecords->fetch()) {
|
||||
// Early continue if record has been migrated before
|
||||
if ($this->isOverlayRecordMigratedAlready((int)$overlayRecord['uid'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values = [];
|
||||
$originalPageId = (int)$overlayRecord['pid'];
|
||||
$page = $this->fetchDefaultLanguagePageRecord($originalPageId);
|
||||
if (!empty($page)) {
|
||||
foreach ($pagesColumns as $pageColumn) {
|
||||
$name = $pageColumn->getName();
|
||||
if (isset($overlayRecord[$name])) {
|
||||
$values[$name] = $overlayRecord[$name];
|
||||
} elseif (isset($page[$name])) {
|
||||
$values[$name] = $page[$name];
|
||||
}
|
||||
}
|
||||
|
||||
$values['pid'] = $page['pid'];
|
||||
$values['l10n_parent'] = $originalPageId;
|
||||
$values['l10n_source'] = $originalPageId;
|
||||
$values['legacy_overlay_uid'] = $overlayRecord['uid'];
|
||||
unset($values['uid']);
|
||||
$pagesConnection->insert(
|
||||
'pages',
|
||||
$values,
|
||||
$pagesColumnTypes
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline relations with foreign_field, foreign_table, foreign_table_field on
|
||||
* pages_language_overlay TCA get their existing relations updated to new
|
||||
* uid and pages table.
|
||||
*/
|
||||
protected function updateInlineRelations()
|
||||
{
|
||||
if (isset($GLOBALS['TCA']['pages_language_overlay']['columns']) && is_array($GLOBALS['TCA']['pages_language_overlay']['columns'])) {
|
||||
foreach ($GLOBALS['TCA']['pages_language_overlay']['columns'] as $fieldName => $fieldConfiguration) {
|
||||
// Migrate any 1:n relations
|
||||
if ($fieldConfiguration['config']['type'] === 'inline'
|
||||
&& !empty($fieldConfiguration['config']['foreign_field'])
|
||||
&& !empty($fieldConfiguration['config']['foreign_table'])
|
||||
&& !empty($fieldConfiguration['config']['foreign_table_field'])
|
||||
) {
|
||||
$foreignTable = trim($fieldConfiguration['config']['foreign_table']);
|
||||
$foreignField = trim($fieldConfiguration['config']['foreign_field']);
|
||||
$foreignTableField = trim($fieldConfiguration['config']['foreign_table_field']);
|
||||
$translatedPagesQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$translatedPagesQueryBuilder->getRestrictions()->removeAll();
|
||||
$translatedPagesRows = $translatedPagesQueryBuilder
|
||||
->select('uid', 'legacy_overlay_uid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$translatedPagesQueryBuilder->expr()->gt(
|
||||
'l10n_parent',
|
||||
$translatedPagesQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
while ($translatedPageRow = $translatedPagesRows->fetch()) {
|
||||
$foreignTableQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($foreignTable);
|
||||
$foreignTableQueryBuilder->getRestrictions()->removeAll();
|
||||
$foreignTableQueryBuilder
|
||||
->update($foreignTable)
|
||||
->set($foreignField, $translatedPageRow['uid'])
|
||||
->set($foreignTableField, 'pages')
|
||||
->where(
|
||||
$foreignTableQueryBuilder->expr()->eq(
|
||||
$foreignField,
|
||||
$foreignTableQueryBuilder->createNamedParameter($translatedPageRow['legacy_overlay_uid'], \PDO::PARAM_INT)
|
||||
),
|
||||
$foreignTableQueryBuilder->expr()->eq(
|
||||
$foreignTableField,
|
||||
$foreignTableQueryBuilder->createNamedParameter('pages_language_overlay', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update recuid and tablename of sys_history table to pages and new uid
|
||||
* for all pages_language_overlay rows
|
||||
*/
|
||||
protected function updateSysHistoryRelations()
|
||||
{
|
||||
$translatedPagesQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$translatedPagesQueryBuilder->getRestrictions()->removeAll();
|
||||
$translatedPagesRows = $translatedPagesQueryBuilder
|
||||
->select('uid', 'legacy_overlay_uid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$translatedPagesQueryBuilder->expr()->gt(
|
||||
'l10n_parent',
|
||||
$translatedPagesQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
while ($translatedPageRow = $translatedPagesRows->fetch()) {
|
||||
$historyTableQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
|
||||
$historyTableQueryBuilder->getRestrictions()->removeAll();
|
||||
$historyTableQueryBuilder
|
||||
->update('sys_history')
|
||||
->set('tablename', 'pages')
|
||||
->set('recuid', $translatedPageRow['uid'])
|
||||
->where(
|
||||
$historyTableQueryBuilder->expr()->eq(
|
||||
'recuid',
|
||||
$historyTableQueryBuilder->createNamedParameter($translatedPageRow['legacy_overlay_uid'], \PDO::PARAM_INT)
|
||||
),
|
||||
$historyTableQueryBuilder->expr()->eq(
|
||||
'tablename',
|
||||
$historyTableQueryBuilder->createNamedParameter('pages_language_overlay', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a certain page
|
||||
*
|
||||
* @param int $pageId
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchDefaultLanguagePageRecord(int $pageId): array
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$page = $queryBuilder
|
||||
->select('*')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute()
|
||||
->fetch();
|
||||
return $page ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if a single overlay record has been migrated to pages already
|
||||
* by checking the db field legacy_overlay_uid for the orig uid
|
||||
*
|
||||
* @param int $overlayUid
|
||||
* @return bool
|
||||
*/
|
||||
protected function isOverlayRecordMigratedAlready(int $overlayUid): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$migratedRecord = $queryBuilder
|
||||
->select('uid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'legacy_overlay_uid',
|
||||
$queryBuilder->createNamedParameter($overlayUid, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute()
|
||||
->fetch();
|
||||
return !empty($migratedRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the database table "pages_language_overlay" exists and if so, if there are entries in the DB table.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function checkIfWizardIsRequired(): bool
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$connection = $connectionPool->getConnectionByName('Default');
|
||||
$tableNames = $connection->getSchemaManager()->listTableNames();
|
||||
if (in_array('pages_language_overlay', $tableNames, true)) {
|
||||
// table is available, now check if there are entries in it
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('pages_language_overlay');
|
||||
$numberOfEntries = $queryBuilder->count('*')
|
||||
->from('pages_language_overlay')
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
return (bool)$numberOfEntries;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the update wizard is run through, the feature to not load any pages_language_overlay data can
|
||||
* be activated.
|
||||
*
|
||||
* Basically writes 'SYS/features/unifiedPageTranslationHandling' to LocalConfiguration.php
|
||||
*/
|
||||
protected function enableFeatureFlag()
|
||||
{
|
||||
GeneralUtility::makeInstance(ConfigurationManager::class)->enableFeature('unifiedPageTranslationHandling');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Merge URLs divided in pages.urltype and pages.url into pages.url
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MigrateUrlTypesInPagesUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
private $databaseTables = ['pages', 'pages_language_overlay'];
|
||||
private $urltypes = ['', 'http://', 'ftp://', 'mailto:', 'https://'];
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'pagesUrltypeField';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate pages.urltype to pages.url';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The page property "URL Protocol" for external URLs has been merged into the URL itself.'
|
||||
. ' The update wizard takes care of properly populating all existing pages and page translations.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (true) or not (false)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
if (!$this->checkIfWizardIsRequired()) {
|
||||
return false;
|
||||
}
|
||||
$recordsToMigrate = 0;
|
||||
// Check if there is data to migrate
|
||||
foreach ($this->databaseTables as $databaseTable) {
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable($databaseTable);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$recordsToMigrate = $queryBuilder->count('*')
|
||||
->from($databaseTable)
|
||||
->where(
|
||||
$queryBuilder->expr()->neq('urltype', 0),
|
||||
$queryBuilder->expr()->neq('url', $queryBuilder->createPositionalParameter(''))
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
|
||||
if ($recordsToMigrate > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $recordsToMigrate > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves data from pages.urltype to pages.url
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
foreach ($this->databaseTables as $databaseTable) {
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable($databaseTable);
|
||||
|
||||
// Process records that have entries in pages.urltype
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'urltype', 'url')
|
||||
->from($databaseTable)
|
||||
->where(
|
||||
$queryBuilder->expr()->neq('urltype', 0),
|
||||
$queryBuilder->expr()->neq('url', $queryBuilder->createPositionalParameter(''))
|
||||
)
|
||||
->execute();
|
||||
|
||||
while ($row = $statement->fetch()) {
|
||||
$url = $this->urltypes[(int)$row['urltype']] . $row['url'];
|
||||
$updateQueryBuilder = $connection->createQueryBuilder();
|
||||
$updateQueryBuilder
|
||||
->update($databaseTable)
|
||||
->where(
|
||||
$updateQueryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$updateQueryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('url', $updateQueryBuilder->createNamedParameter($url), false)
|
||||
->set('urltype', 0);
|
||||
$updateQueryBuilder->execute();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check each table if the column exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkIfWizardIsRequired(): bool
|
||||
{
|
||||
foreach ($this->databaseTables as $key => $databaseTable) {
|
||||
$columns = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable($databaseTable)
|
||||
->getSchemaManager()
|
||||
->listTableColumns($databaseTable);
|
||||
if (!isset($columns['urltype'])) {
|
||||
unset($this->databaseTables[$key]);
|
||||
}
|
||||
}
|
||||
return count($this->databaseTables) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
|
||||
use TYPO3\CMS\Core\DataHandling\SlugHelper;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Fills pages.slug with a proper value for pages that do not have a slug updater.
|
||||
* Does not take "deleted" pages into account, but respects workspace records.
|
||||
*
|
||||
* This is how it works:
|
||||
* - Check if a page has pages.alias filled.
|
||||
* - Check if realurl v1 (tx_realurl_pathcache) or v2 (tx_realurl_pathdata) has a page path, use that instead.
|
||||
* - If not -> generate the slug.
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PopulatePageSlugs implements UpgradeWizardInterface
|
||||
{
|
||||
protected $table = 'pages';
|
||||
|
||||
protected $fieldName = 'slug';
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'pagesSlugs';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Introduce URL parts ("slugs") to all existing pages';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'TYPO3 includes native URL handling. Every page record has its own speaking URL path'
|
||||
. ' called "slug" which can be edited in TYPO3 Backend. However, it is necessary that all pages have'
|
||||
. ' a URL pre-filled. This is done by evaluating the page title / navigation title and all of its rootline.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether updates are required.
|
||||
*
|
||||
* @return bool Whether an update is required (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$updateNeeded = false;
|
||||
// Check if the database table even exists
|
||||
if ($this->checkIfWizardIsRequired()) {
|
||||
$updateNeeded = true;
|
||||
}
|
||||
return $updateNeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the accordant updates.
|
||||
*
|
||||
* @return bool Whether everything went smoothly or not
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$this->populateSlugs();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the database table "pages" with slugs based on the page title and its configuration.
|
||||
* But also checks "legacy" functionality.
|
||||
*/
|
||||
protected function populateSlugs()
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$statement = $queryBuilder
|
||||
->select('*')
|
||||
->from($this->table)
|
||||
->where(
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->eq($this->fieldName, $queryBuilder->createNamedParameter('')),
|
||||
$queryBuilder->expr()->isNull($this->fieldName)
|
||||
)
|
||||
)
|
||||
// Ensure that fields with alias are managed first
|
||||
->orderBy('alias', 'desc')
|
||||
// Ensure that live workspace records are handled first
|
||||
->addOrderBy('t3ver_wsid', 'asc')
|
||||
// Ensure that all pages are run through "per parent page" field, and in the correct sorting values
|
||||
->addOrderBy('pid', 'asc')
|
||||
->addOrderBy('sorting', 'asc')
|
||||
->execute();
|
||||
|
||||
// Check for existing slugs from realurl
|
||||
$suggestedSlugs = [];
|
||||
if ($this->checkIfTableExists('tx_realurl_pathdata')) {
|
||||
$suggestedSlugs = $this->getSuggestedSlugs('tx_realurl_pathdata');
|
||||
} elseif ($this->checkIfTableExists('tx_realurl_pathcache')) {
|
||||
$suggestedSlugs = $this->getSuggestedSlugs('tx_realurl_pathcache', 'cache_id');
|
||||
}
|
||||
|
||||
$fieldConfig = $GLOBALS['TCA'][$this->table]['columns'][$this->fieldName]['config'];
|
||||
$fieldConfig['generatorOptions']['fields'] = ['tx_realurl_pathsegment,title'];
|
||||
$evalInfo = !empty($fieldConfig['eval']) ? GeneralUtility::trimExplode(',', $fieldConfig['eval'], true) : [];
|
||||
$hasToBeUniqueInSite = in_array('uniqueInSite', $evalInfo, true);
|
||||
$hasToBeUniqueInPid = in_array('uniqueInPid', $evalInfo, true);
|
||||
$slugHelper = GeneralUtility::makeInstance(SlugHelper::class, $this->table, $this->fieldName, $fieldConfig);
|
||||
while ($record = $statement->fetch()) {
|
||||
$recordId = (int)$record['uid'];
|
||||
$pid = (int)$record['pid'];
|
||||
$languageId = (int)$record['sys_language_uid'];
|
||||
$pageIdInDefaultLanguage = $languageId > 0 ? (int)$record['l10n_parent'] : $recordId;
|
||||
$slug = $suggestedSlugs[$pageIdInDefaultLanguage][$languageId] ?? '';
|
||||
|
||||
// see if an alias field was used, then let's build a slug out of that.
|
||||
if (!empty($record['alias'])) {
|
||||
$slug = $slugHelper->sanitize('/' . $record['alias']);
|
||||
}
|
||||
|
||||
if (empty($slug)) {
|
||||
if ($pid === -1) {
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$liveVersion = $queryBuilder
|
||||
->select('pid')
|
||||
->from('pages')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($record['t3ver_oid'], \PDO::PARAM_INT))
|
||||
)->execute()->fetch();
|
||||
$pid = (int)$liveVersion['pid'];
|
||||
}
|
||||
$slug = $slugHelper->generate($record, $pid);
|
||||
}
|
||||
|
||||
$state = RecordStateFactory::forName($this->table)
|
||||
->fromArray($record, $pid, $recordId);
|
||||
if ($hasToBeUniqueInSite && !$slugHelper->isUniqueInSite($slug, $state)) {
|
||||
$slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
|
||||
}
|
||||
if ($hasToBeUniqueInPid && !$slugHelper->isUniqueInPid($slug, $state)) {
|
||||
$slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
|
||||
}
|
||||
|
||||
$connection->update(
|
||||
$this->table,
|
||||
[$this->fieldName => $slug],
|
||||
['uid' => $recordId]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are record within "pages" database table with an empty "slug" field.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
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($this->table)
|
||||
->where(
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->eq($this->fieldName, $queryBuilder->createNamedParameter('')),
|
||||
$queryBuilder->expr()->isNull($this->fieldName)
|
||||
)
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
return $numberOfEntries > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve prepared realurl "pagepath" for pages
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $identityField
|
||||
* @return array with pageID (default language) and language ID as two-dimensional array containing the page path
|
||||
*/
|
||||
protected function getSuggestedSlugs(string $tableName, string $identityField = 'uid'): array
|
||||
{
|
||||
$context = GeneralUtility::makeInstance(Context::class);
|
||||
$currentTimestamp = $context->getPropertyFromAspect('date', 'timestamp');
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
|
||||
$statement = $queryBuilder
|
||||
->select('*')
|
||||
->from($tableName)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('mpvar', $queryBuilder->createNamedParameter('')),
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->eq('expire', $queryBuilder->createNamedParameter(0)),
|
||||
$queryBuilder->expr()->gt('expire', $queryBuilder->createNamedParameter($currentTimestamp))
|
||||
)
|
||||
)
|
||||
->orderBy('expire', 'ASC')
|
||||
->execute();
|
||||
$suggestedSlugs = [];
|
||||
while ($row = $statement->fetch()) {
|
||||
// rawurldecode ensures that non-ASCII arguments are also migrated
|
||||
$pagePath = rawurldecode($row['pagepath']);
|
||||
if (!isset($suggestedSlugs[(int)$row['page_id']][(int)$row['language_id']])) { // keep only first result
|
||||
$suggestedSlugs[(int)$row['page_id']][(int)$row['language_id']] = '/' . trim($pagePath, '/');
|
||||
}
|
||||
}
|
||||
return $suggestedSlugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given table exists
|
||||
*
|
||||
* @param string $table
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkIfTableExists($table)
|
||||
{
|
||||
$tableExists = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable($table)
|
||||
->getSchemaManager()
|
||||
->tablesExist([$table]);
|
||||
|
||||
return $tableExists;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
class PrerequisiteCollection implements \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var \ArrayObject
|
||||
*/
|
||||
protected $prerequisites;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->prerequisites = new \ArrayObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prerequisiteClass
|
||||
*/
|
||||
public function add(string $prerequisiteClass): void
|
||||
{
|
||||
if (
|
||||
!($this->prerequisites[$prerequisiteClass] ?? false)
|
||||
&& is_a($prerequisiteClass, PrerequisiteInterface::class, true)
|
||||
) {
|
||||
$this->prerequisites[$prerequisiteClass] = GeneralUtility::makeInstance(
|
||||
$prerequisiteClass
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \ArrayObject|\Traversable|PrerequisiteInterface[]
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return $this->prerequisites;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* UpgradeWizard Prerequisites
|
||||
*/
|
||||
interface PrerequisiteInterface
|
||||
{
|
||||
/**
|
||||
* Get speaking name of this prerequisite
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string;
|
||||
|
||||
/**
|
||||
* Ensure this prerequisite is fulfilled
|
||||
*
|
||||
* Gets called if "isFulfilled" returns false
|
||||
* and should ensure the prerequisite
|
||||
*
|
||||
* Returns true on success, false on error
|
||||
*
|
||||
* @see isFulfilled
|
||||
* @return bool
|
||||
*/
|
||||
public function ensure(): bool;
|
||||
|
||||
/**
|
||||
* Is this prerequisite met?
|
||||
*
|
||||
* Checks whether this prerequisite is fulfilled. If it is not,
|
||||
* ensure should be called to fulfill it.
|
||||
*
|
||||
* @see ensure
|
||||
* @return bool
|
||||
*/
|
||||
public function isFulfilled(): bool;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Installs and downloads EXT:rdct if cache_md5params is filled
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class RedirectExtractionUpdate extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\ExtensionModel
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'rdct',
|
||||
'Redirects based on &RDCT parameter',
|
||||
'1.0.0',
|
||||
'friendsoftypo3/rdct',
|
||||
'The extension provides redirects based on "cache_md5params" and the GET parameter &RDCT for extensions that still rely on it.'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'You should install the Redirects extension only if needed. ' . $this->extension->getDescription(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'rdctExtension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install extension "rdct" from TER if DB table cache_md5params is filled';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The extension "rdct" includes redirects based on the GET parameter &RDCT. The functionality has been extracted to'
|
||||
. ' the TYPO3 Extension Repository. This update downloads the TYPO3 extension from the TER.'
|
||||
. ' Use this if you are dealing with extensions in the instance that rely on this kind of redirects.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !ExtensionManagementUtility::isLoaded('rdct') && $this->checkIfWizardIsRequired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the database table "cache_md5params" exists and if so, if there are entries in the DB table.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function checkIfWizardIsRequired(): bool
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$connection = $connectionPool->getConnectionByName('Default');
|
||||
$tableNames = $connection->getSchemaManager()->listTableNames();
|
||||
if (in_array('cache_md5params', $tableNames, true)) {
|
||||
// table is available, now check if there are entries in it
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('cache_md5params');
|
||||
$numberOfEntries = $queryBuilder->count('*')
|
||||
->from('cache_md5params')
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
return (bool)$numberOfEntries;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Installs EXT:redirect if sys_domain.redirectTo is filled, and migrates the values from redirectTo
|
||||
* to a proper sys_redirect entry.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class RedirectsExtensionUpdate extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'redirects',
|
||||
'Redirects',
|
||||
'9.2',
|
||||
'typo3/cms-redirects',
|
||||
'Manage redirects for your TYPO3-based website'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'You should install the "redirects" extension only if needed. ' . $this->extension->getDescription(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'redirects';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install system extension "redirects" if a sys_domain entry with redirectTo is necessary';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The extension "redirects" includes functionality to handle any kind of redirects. '
|
||||
. 'The functionality superseds sys_domain entries with the only purpose of redirecting to a different domain or entry. '
|
||||
. 'This upgrade wizard installs the redirect extension if necessary and migrates the sys_domain entries to standard redirects.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return $this->checkIfWizardIsRequired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the update:
|
||||
* - Install EXT:redirect
|
||||
* - Migrate DB records
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
// Install the EXT:redirects extension if not happened yet
|
||||
$installationSuccessful = $this->installExtension($this->extension);
|
||||
if ($installationSuccessful) {
|
||||
// Migrate the database entries
|
||||
$this->migrateRedirectDomainsToSysRedirect();
|
||||
}
|
||||
return $installationSuccessful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the database field "sys_domain.redirectTo" exists and if so, if there are entries in the DB table with the field filled.
|
||||
*
|
||||
* @return bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function checkIfWizardIsRequired(): bool
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$connection = $connectionPool->getConnectionByName('Default');
|
||||
$columns = $connection->getSchemaManager()->listTableColumns('sys_domain');
|
||||
if (isset($columns['redirectto'])) {
|
||||
// table is available, now check if there are entries in it
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_domain');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$numberOfEntries = $queryBuilder->count('*')
|
||||
->from('sys_domain')
|
||||
->where(
|
||||
$queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn();
|
||||
return (bool)$numberOfEntries;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all sys_domain records with a "redirectTo" value filled (also deleted) to "sys_redirect" record
|
||||
*/
|
||||
protected function migrateRedirectDomainsToSysRedirect()
|
||||
{
|
||||
$connDomains = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_domain');
|
||||
$connRedirects = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_redirect');
|
||||
|
||||
$queryBuilder = $connDomains->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$domainEntries = $queryBuilder->select('*')
|
||||
->from('sys_domain')
|
||||
->where(
|
||||
$queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
|
||||
)
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
foreach ($domainEntries as $domainEntry) {
|
||||
$domainName = $domainEntry['domainName'];
|
||||
$target = $domainEntry['redirectTo'];
|
||||
$sourceDetails = parse_url($domainName);
|
||||
$targetDetails = parse_url($target);
|
||||
$redirectRecord = [
|
||||
'deleted' => (int)$domainEntry['deleted'],
|
||||
'disabled' => (int)$domainEntry['hidden'],
|
||||
'createdon' => (int)$domainEntry['crdate'],
|
||||
'createdby' => (int)$domainEntry['cruser_id'],
|
||||
'updatedon' => (int)$domainEntry['tstamp'],
|
||||
'source_host' => $sourceDetails['host'] . ($sourceDetails['port'] ? ':' . $sourceDetails['port'] : ''),
|
||||
'keep_query_parameters' => (int)$domainEntry['prepend_params'],
|
||||
'target_statuscode' => (int)$domainEntry['redirectHttpStatusCode'],
|
||||
'target' => $target
|
||||
];
|
||||
|
||||
if (isset($targetDetails['scheme']) && $targetDetails['scheme'] === 'https') {
|
||||
$redirectRecord['force_https'] = 1;
|
||||
}
|
||||
|
||||
if (empty($sourceDetails['path']) || $sourceDetails['path'] === '/') {
|
||||
$redirectRecord['source_path'] = '#.*#';
|
||||
$redirectRecord['is_regexp'] = 1;
|
||||
} else {
|
||||
// Remove the / and add a "/" always before, and at the very end, if path is not empty
|
||||
$sourceDetails['path'] = trim($sourceDetails['path'], '/');
|
||||
$redirectRecord['source_path'] = '/' . ($sourceDetails['path'] ? $sourceDetails['path'] . '/' : '');
|
||||
}
|
||||
|
||||
// Add the redirect record
|
||||
$connRedirects->insert('sys_redirect', $redirectRecord);
|
||||
|
||||
// Remove the sys_domain record (hard)
|
||||
$connDomains->delete('sys_domain', ['uid' => (int)$domainEntry['uid']]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Database\ReferenceIndex;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* ReferenceIndex Prerequisite
|
||||
*
|
||||
* Defines that the reference index needs to be up-to-date before an upgrade wizard may be run
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ReferenceIndexUpdatedPrerequisite implements PrerequisiteInterface, ChattyInterface
|
||||
{
|
||||
/**
|
||||
* @var object|\TYPO3\CMS\Core\Database\ReferenceIndex
|
||||
*/
|
||||
private $referenceIndex;
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* ReferenceIndexUpdatedPrerequisite constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Reference Index Up-to-Date';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the reference index
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function ensure(): bool
|
||||
{
|
||||
$this->referenceIndex->enableRuntimeCache();
|
||||
ob_clean();
|
||||
ob_start();
|
||||
$result = $this->referenceIndex->updateIndex(false, true);
|
||||
$output = ob_get_clean();
|
||||
$this->output->write($output);
|
||||
return $result[2] === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there are reference index updates to be done
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFulfilled(): bool
|
||||
{
|
||||
$this->referenceIndex->enableRuntimeCache();
|
||||
$result = $this->referenceIndex->updateIndex(true, false);
|
||||
return $result[2] === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Use if wizard may be run multiple times (and should not be disabled after one run)
|
||||
*
|
||||
* Semantic/Marker interface only
|
||||
*/
|
||||
interface RepeatableInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates\RowUpdater;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
|
||||
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Migrate values for database records having columns
|
||||
* using "l10n_mode" set to "mergeIfNotBlank".
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ImageCropUpdater implements RowUpdaterInterface
|
||||
{
|
||||
/**
|
||||
* List of tables with information about to migrate fields.
|
||||
* Created during hasPotentialUpdateForTable(), used in updateTableRow()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $payload = [];
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate values in sys_file_reference crop field';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a table needs modifications.
|
||||
*
|
||||
* @param string $tableName Table name to check
|
||||
* @return bool True if this table has fields to migrate
|
||||
*/
|
||||
public function hasPotentialUpdateForTable(string $tableName): bool
|
||||
{
|
||||
$result = false;
|
||||
$payload = $this->getPayloadForTable($tableName);
|
||||
if (count($payload) !== 0) {
|
||||
$this->payload[$tableName] = $payload;
|
||||
$result = true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update single row if needed
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param array $inputRow Given row data
|
||||
* @return array Modified row data
|
||||
*/
|
||||
public function updateTableRow(string $tableName, array $inputRow): array
|
||||
{
|
||||
$tablePayload = $this->payload[$tableName];
|
||||
|
||||
foreach ($tablePayload['fields'] as $field) {
|
||||
if (strpos($inputRow[$field], '{"x":') === 0) {
|
||||
$cropArray = json_decode($inputRow[$field], true);
|
||||
if (is_array($cropArray)) {
|
||||
$file = $this->getFile($inputRow, $tablePayload['fileReferenceField'] ?: 'uid_local');
|
||||
if (null === $file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cropArea = Area::createFromConfiguration(json_decode($inputRow[$field], true));
|
||||
$cropVariantCollectionConfig = [
|
||||
'default' => [
|
||||
'cropArea' => $cropArea->makeRelativeBasedOnFile($file)->asArray(),
|
||||
]
|
||||
];
|
||||
$inputRow[$field] = json_encode($cropVariantCollectionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $inputRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves field names grouped per table name having "l10n_mode" set
|
||||
* to a relevant value that shall be migrated in database records.
|
||||
*
|
||||
* Resulting array is structured like this:
|
||||
* + fields: [field a, field b, ...]
|
||||
* + sources
|
||||
* + source uid: [localization uid, localization uid, ...]
|
||||
*
|
||||
* @param string $tableName Table name
|
||||
* @return array Payload information for this table
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getPayloadForTable(string $tableName): array
|
||||
{
|
||||
if (!is_array($GLOBALS['TCA'][$tableName])) {
|
||||
throw new \RuntimeException(
|
||||
'Globals TCA of given table name must exist',
|
||||
1485386982
|
||||
);
|
||||
}
|
||||
$tableDefinition = $GLOBALS['TCA'][$tableName];
|
||||
|
||||
if (
|
||||
empty($tableDefinition['columns'])
|
||||
|| !is_array($tableDefinition['columns'])
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
$fileReferenceField = null;
|
||||
foreach ($tableDefinition['columns'] as $fieldName => $fieldConfiguration) {
|
||||
if (
|
||||
!empty($fieldConfiguration['config']['type'])
|
||||
&& $fieldConfiguration['config']['type'] === 'group'
|
||||
&& !empty($fieldConfiguration['config']['internal_type'])
|
||||
&& $fieldConfiguration['config']['internal_type'] === 'db'
|
||||
&& !empty($fieldConfiguration['config']['allowed'])
|
||||
&& $fieldConfiguration['config']['allowed'] === 'sys_file'
|
||||
) {
|
||||
$fileReferenceField = $fieldName;
|
||||
}
|
||||
if (
|
||||
!empty($fieldConfiguration['config']['type'])
|
||||
&& $fieldConfiguration['config']['type'] === 'imageManipulation'
|
||||
) {
|
||||
$fields[] = $fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$payload = [];
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
|
||||
foreach ($fields as $fieldName) {
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
|
||||
$query = $queryBuilder
|
||||
->from($tableName)
|
||||
->count($fieldName)
|
||||
->where(
|
||||
$queryBuilder->expr()->like(
|
||||
$fieldName,
|
||||
$queryBuilder->createNamedParameter('{"x":%', \PDO::PARAM_STR)
|
||||
)
|
||||
);
|
||||
if ((int)$query->execute()->fetchColumn(0) > 0) {
|
||||
$payload['fields'][] = $fieldName;
|
||||
if (isset($fileReferenceField)) {
|
||||
$payload['fileReferenceField'] = $fileReferenceField;
|
||||
} else {
|
||||
$payload['fileReferenceField'] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file object
|
||||
*
|
||||
* @param array $row
|
||||
* @param string $fieldName
|
||||
* @return \TYPO3\CMS\Core\Resource\File|null
|
||||
*/
|
||||
private function getFile(array $row, $fieldName)
|
||||
{
|
||||
$file = null;
|
||||
$fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : null;
|
||||
if (is_array($fileUid) && isset($fileUid[0]['uid'])) {
|
||||
$fileUid = $fileUid[0]['uid'];
|
||||
}
|
||||
if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
|
||||
try {
|
||||
$file = ResourceFactory::getInstance()->getFileObject((int)$fileUid);
|
||||
} catch (FileDoesNotExistException $e) {
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates\RowUpdater;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\DataHandling\Localization\State;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Versioning\VersionState;
|
||||
|
||||
/**
|
||||
* Migrate values for database records having columns
|
||||
* using "l10n_mode" set to "mergeIfNotBlank" or "exclude".
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class L10nModeUpdater implements RowUpdaterInterface
|
||||
{
|
||||
/**
|
||||
* List of tables with information about to migrate fields.
|
||||
* Created during hasPotentialUpdateForTable(), used in updateTableRow()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $payload = [];
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate values in database records having "l10n_mode"'
|
||||
. ' either set to "exclude" or "mergeIfNotBlank"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a table needs modifications.
|
||||
*
|
||||
* @param string $tableName Table name to check
|
||||
* @return bool True if this table has fields to migrate
|
||||
*/
|
||||
public function hasPotentialUpdateForTable(string $tableName): bool
|
||||
{
|
||||
$this->payload[$tableName] = $this->getL10nModePayloadForTable($tableName);
|
||||
return !empty($this->payload[$tableName]['localizations']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update single row if needed
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param array $inputRow Given row data
|
||||
* @return array Modified row data
|
||||
*/
|
||||
public function updateTableRow(string $tableName, array $inputRow): array
|
||||
{
|
||||
$currentId = (int)$inputRow['uid'];
|
||||
|
||||
if (empty($this->payload[$tableName]['localizations'][$currentId])) {
|
||||
return $inputRow;
|
||||
}
|
||||
|
||||
// disable DataHandler hooks for processing this update
|
||||
if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'])) {
|
||||
$dataHandlerHooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'];
|
||||
unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']);
|
||||
}
|
||||
|
||||
if (empty($GLOBALS['LANG'])) {
|
||||
$GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageService::class);
|
||||
}
|
||||
if (!empty($GLOBALS['BE_USER'])) {
|
||||
$adminUser = $GLOBALS['BE_USER'];
|
||||
}
|
||||
// the admin user is required to defined workspace state when working with DataHandler
|
||||
$fakeAdminUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
|
||||
$fakeAdminUser->user = ['uid' => 0, 'username' => '_migration_', 'admin' => 1];
|
||||
$fakeAdminUser->workspace = (int)($inputRow['t3ver_wsid'] ?? 0);
|
||||
$GLOBALS['BE_USER'] = $fakeAdminUser;
|
||||
|
||||
$tablePayload = $this->payload[$tableName];
|
||||
|
||||
$liveId = $currentId;
|
||||
if (!empty($inputRow['t3ver_wsid'])
|
||||
&& !empty($inputRow['t3ver_oid'])
|
||||
&& !VersionState::cast($inputRow['t3ver_state'])
|
||||
->equals(VersionState::NEW_PLACEHOLDER_VERSION)) {
|
||||
$liveId = (int)$inputRow['t3ver_oid'];
|
||||
}
|
||||
|
||||
$dataMap = [];
|
||||
|
||||
// define localization states and thus trigger updates later
|
||||
if (State::isApplicable($tableName)) {
|
||||
$stateUpdates = [];
|
||||
foreach ($tablePayload['fieldModes'] as $fieldName => $fieldMode) {
|
||||
if ($fieldMode !== 'mergeIfNotBlank') {
|
||||
continue;
|
||||
}
|
||||
if (!empty($inputRow[$fieldName])) {
|
||||
$stateUpdates[$fieldName] = State::STATE_CUSTOM;
|
||||
} else {
|
||||
$stateUpdates[$fieldName] = State::STATE_PARENT;
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the language state upfront, so that calling DataMapProcessor below
|
||||
// will handle mergeIfNotBlank fields properly
|
||||
$languageState = State::create($tableName);
|
||||
$languageState->update($stateUpdates);
|
||||
$dataMap = [
|
||||
$tableName => [
|
||||
$liveId => [
|
||||
'l10n_state' => $languageState->toArray()
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// simulate modifying a parent record to trigger dependent updates
|
||||
if (in_array('exclude', $tablePayload['fieldModes'], true)) {
|
||||
if ($liveId !== $currentId) {
|
||||
$record = $this->getRow($tableName, $liveId);
|
||||
} else {
|
||||
$record = $inputRow;
|
||||
}
|
||||
foreach ($tablePayload['fieldModes'] as $fieldName => $fieldMode) {
|
||||
if ($fieldMode !== 'exclude') {
|
||||
continue;
|
||||
}
|
||||
$dataMap[$tableName][$liveId][$fieldName] = $record[$fieldName];
|
||||
}
|
||||
}
|
||||
|
||||
// in case $dataMap is empty, nothing has to be updated
|
||||
if (!empty($dataMap)) {
|
||||
// let DataHandler process all updates, $inputRow won't change
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$dataHandler->enableLogging = false;
|
||||
$dataHandler->start($dataMap, [], $fakeAdminUser);
|
||||
$dataHandler->process_datamap();
|
||||
}
|
||||
|
||||
if (!empty($dataHandlerHooks)) {
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'] = $dataHandlerHooks;
|
||||
}
|
||||
if (!empty($adminUser)) {
|
||||
$GLOBALS['BE_USER'] = $adminUser;
|
||||
}
|
||||
|
||||
// the unchanged(!) state as submitted
|
||||
return $inputRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves field names grouped per table name having "l10n_mode" set
|
||||
* to a relevant value that shall be migrated in database records.
|
||||
*
|
||||
* Resulting array is structured like this:
|
||||
* + fields: [field a, field b, ...]
|
||||
* + sources
|
||||
* + source uid: [localization uid, localization uid, ...]
|
||||
*
|
||||
* @param string $tableName Table name
|
||||
* @return array Payload information for this table
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getL10nModePayloadForTable(string $tableName): array
|
||||
{
|
||||
if (!isset($GLOBALS['TCA'][$tableName]) || !\is_array($GLOBALS['TCA'][$tableName])) {
|
||||
throw new \RuntimeException(
|
||||
'Globals TCA of given table name must exist',
|
||||
1484176136
|
||||
);
|
||||
}
|
||||
|
||||
$tableDefinition = $GLOBALS['TCA'][$tableName];
|
||||
$languageFieldName = ($tableDefinition['ctrl']['languageField'] ?? null);
|
||||
$parentFieldName = ($tableDefinition['ctrl']['transOrigPointerField'] ?? null);
|
||||
|
||||
if (
|
||||
empty($tableDefinition['columns'])
|
||||
|| !is_array($tableDefinition['columns'])
|
||||
|| empty($languageFieldName)
|
||||
|| empty($parentFieldName)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fieldModes = [];
|
||||
foreach ($tableDefinition['columns'] as $fieldName => $fieldConfiguration) {
|
||||
$l10nMode = ($fieldConfiguration['l10n_mode'] ?? null);
|
||||
$allowLanguageSynchronization = ($fieldConfiguration['config']['behaviour']['allowLanguageSynchronization'] ?? null);
|
||||
|
||||
if ($l10nMode === 'exclude') {
|
||||
$fieldModes[$fieldName] = $l10nMode;
|
||||
} elseif ($allowLanguageSynchronization) {
|
||||
$fieldModes[$fieldName] = 'mergeIfNotBlank';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($fieldModes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->from($tableName);
|
||||
|
||||
$parentFieldName = $tableDefinition['ctrl']['transOrigPointerField'];
|
||||
$selectFieldNames = ['uid', $parentFieldName];
|
||||
|
||||
$predicates = [
|
||||
$queryBuilder->expr()->gt(
|
||||
$tableDefinition['ctrl']['languageField'],
|
||||
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->gt(
|
||||
$parentFieldName,
|
||||
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
|
||||
)
|
||||
];
|
||||
|
||||
if (!empty($tableDefinition['ctrl']['versioningWS'])) {
|
||||
$selectFieldNames = array_merge(
|
||||
$selectFieldNames,
|
||||
['t3ver_wsid', 't3ver_oid', 't3ver_state']
|
||||
);
|
||||
$predicates[] = $queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->eq(
|
||||
't3ver_state',
|
||||
$queryBuilder->createNamedParameter(
|
||||
VersionState::NEW_PLACEHOLDER_VERSION,
|
||||
\PDO::PARAM_INT
|
||||
)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
't3ver_state',
|
||||
$queryBuilder->createNamedParameter(
|
||||
VersionState::DEFAULT_STATE,
|
||||
\PDO::PARAM_INT
|
||||
)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
't3ver_state',
|
||||
$queryBuilder->createNamedParameter(
|
||||
VersionState::MOVE_POINTER,
|
||||
\PDO::PARAM_INT
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$statement = $queryBuilder
|
||||
->select(...$selectFieldNames)
|
||||
->andWhere(...$predicates)
|
||||
->execute();
|
||||
|
||||
$payload = [];
|
||||
|
||||
foreach ($statement as $row) {
|
||||
$translationId = $row['uid'];
|
||||
$parentId = (int)$row[$parentFieldName];
|
||||
$payload['localizations'][$translationId] = $parentId;
|
||||
}
|
||||
if (!empty($payload['localizations'])) {
|
||||
$payload['fieldModes'] = $fieldModes;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
protected function getRow(string $tableName, int $id)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable($tableName);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
|
||||
$statement = $queryBuilder
|
||||
->select('*')
|
||||
->from($tableName)
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
|
||||
return $statement->fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates\RowUpdater;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface each single row updater must implement.
|
||||
*/
|
||||
interface RowUpdaterInterface
|
||||
{
|
||||
/**
|
||||
* Get a description of this single row updater
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string;
|
||||
|
||||
/**
|
||||
* Return true if this row updater may have updates for given table rows.
|
||||
*
|
||||
* @param string $tableName Given table
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPotentialUpdateForTable(string $tableName): bool;
|
||||
|
||||
/**
|
||||
* Update a single row from a table.
|
||||
*
|
||||
* @param string $tableName Given table
|
||||
* @param array $row Given row
|
||||
* @return array Potentially modified row
|
||||
*/
|
||||
public function updateTableRow(string $tableName, array $row): array;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates\RowUpdater;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException;
|
||||
use TYPO3\CMS\Core\LinkHandling\Exception\UnknownUrnException;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
|
||||
|
||||
/**
|
||||
* Move '<link ...' syntax to '<a href' in rte fields
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class RteLinkSyntaxUpdater implements RowUpdaterInterface
|
||||
{
|
||||
/**
|
||||
* Table list with field list that may have links them
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tableFieldListToConsider = [];
|
||||
|
||||
/**
|
||||
* @var array Table names that should be ignored.
|
||||
*/
|
||||
protected $blackListedTables = [
|
||||
'sys_log',
|
||||
'sys_history',
|
||||
'sys_template',
|
||||
];
|
||||
|
||||
/**
|
||||
* Regular expressions to match the <link ...>content</link> inside
|
||||
* @var array
|
||||
*/
|
||||
protected $regularExpressions = [
|
||||
'default' => '#
|
||||
(?\'tag\'<link\\s++(?\'typolink\'[^>]+)>)
|
||||
(?\'content\'(?:[^<]++|<(?!/link>))*+)
|
||||
</link>
|
||||
#xumsi',
|
||||
'flex' => '#
|
||||
(?\'tag\'<link\\s++(?\'typolink\'(?:[^&]++|&(?!gt;))++)>)
|
||||
(?\'content\'(?:[^&]++|&(?!lt;/link>))*+)
|
||||
</link>
|
||||
#xumsi'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string Title
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Scan for old "<link>" syntax in richtext and text fields and update to "<a href>"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a table may have RTE fields
|
||||
*
|
||||
* @param string $tableName Table name to check
|
||||
* @return bool True if this table potentially has RTE fields
|
||||
*/
|
||||
public function hasPotentialUpdateForTable(string $tableName): bool
|
||||
{
|
||||
if (!is_array($GLOBALS['TCA'][$tableName])) {
|
||||
throw new \RuntimeException(
|
||||
'Globals TCA of ' . $tableName . ' must be an array',
|
||||
1484173035
|
||||
);
|
||||
}
|
||||
$result = false;
|
||||
if (in_array($tableName, $this->blackListedTables, true)) {
|
||||
return $result;
|
||||
}
|
||||
$tcaOfTable = $GLOBALS['TCA'][$tableName];
|
||||
if (!is_array($tcaOfTable['columns'])) {
|
||||
return $result;
|
||||
}
|
||||
foreach ($tcaOfTable['columns'] as $fieldName => $fieldConfiguration) {
|
||||
if (isset($fieldConfiguration['config']['type'])
|
||||
&& in_array($fieldConfiguration['config']['type'], ['input', 'text', 'flex'], true)
|
||||
) {
|
||||
$result = true;
|
||||
if (!is_array($this->tableFieldListToConsider[$tableName])) {
|
||||
$this->tableFieldListToConsider[$tableName] = [];
|
||||
}
|
||||
$this->tableFieldListToConsider[$tableName][] = $fieldName;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update "<link" tags in RTE fields
|
||||
*
|
||||
* @param string $tableName Table name
|
||||
* @param array $row Given row data
|
||||
* @return array Modified row data
|
||||
*/
|
||||
public function updateTableRow(string $tableName, array $row): array
|
||||
{
|
||||
if (!is_array($this->tableFieldListToConsider)) {
|
||||
throw new \RuntimeException(
|
||||
'Parent should not call me with a table name I do not consider relevant for update',
|
||||
1484173650
|
||||
);
|
||||
}
|
||||
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
|
||||
$fieldsToScan = $this->tableFieldListToConsider[$tableName];
|
||||
foreach ($fieldsToScan as $fieldName) {
|
||||
$row[$fieldName] = $this->transformLinkTagsIfFound(
|
||||
$tableName,
|
||||
$fieldName,
|
||||
$row,
|
||||
$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'] === 'flex'
|
||||
);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all <link> tags and calls the typolink codec service and the link service (twice) to get a string
|
||||
* representation of the href part, and then builds an anchor tag.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $fieldName
|
||||
* @param array $row
|
||||
* @param bool $isFlexformField If true the content is htmlspecialchar()'d and must be treated as such
|
||||
* @return mixed the modified content
|
||||
*/
|
||||
protected function transformLinkTagsIfFound(string $tableName, string $fieldName, array $row, bool $isFlexformField)
|
||||
{
|
||||
$content = $row[$fieldName];
|
||||
if (is_string($content)
|
||||
&& !empty($content)
|
||||
&& (stripos($content, '<link') !== false || stripos($content, '<link') !== false)
|
||||
) {
|
||||
$result = preg_replace_callback(
|
||||
$this->regularExpressions[$isFlexformField ? 'flex' : 'default'],
|
||||
function ($matches) use ($isFlexformField) {
|
||||
$typoLink = $isFlexformField ? htmlspecialchars_decode($matches['typolink']) : $matches['typolink'];
|
||||
$typoLinkParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typoLink);
|
||||
$anchorTagAttributes = [
|
||||
'target' => $typoLinkParts['target'],
|
||||
'class' => $typoLinkParts['class'],
|
||||
'title' => $typoLinkParts['title'],
|
||||
];
|
||||
|
||||
$link = $typoLinkParts['url'];
|
||||
if (!empty($typoLinkParts['additionalParams'])) {
|
||||
$link .= (strpos($link, '?') === false ? '?' : '&') . ltrim($typoLinkParts['additionalParams'], '&');
|
||||
}
|
||||
|
||||
try {
|
||||
$linkService = GeneralUtility::makeInstance(LinkService::class);
|
||||
// Ensure the old syntax is converted to the new t3:// syntax, if necessary
|
||||
$linkParts = $linkService->resolve($link);
|
||||
$anchorTagAttributes['href'] = $linkService->asString($linkParts);
|
||||
$newLink = '<a ' . GeneralUtility::implodeAttributes($anchorTagAttributes, true) . '>' .
|
||||
($isFlexformField ? htmlspecialchars_decode($matches['content']) : $matches['content']) .
|
||||
'</a>';
|
||||
if ($isFlexformField) {
|
||||
$newLink = htmlspecialchars($newLink);
|
||||
}
|
||||
} catch (UnknownLinkHandlerException $e) {
|
||||
$newLink = $matches[0];
|
||||
} catch (UnknownUrnException $e) {
|
||||
$newLink = $matches[0];
|
||||
}
|
||||
|
||||
return $newLink;
|
||||
},
|
||||
$content
|
||||
);
|
||||
if ($result !== null) {
|
||||
$content = $result;
|
||||
} else {
|
||||
$this->logger->error('Converting links failed due to PCRE error', [
|
||||
'table' => $tableName,
|
||||
'field' => $fieldName,
|
||||
'uid' => $row['uid'] ?? null,
|
||||
'errorCode' => preg_last_error()
|
||||
]);
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
|
||||
/**
|
||||
* Installs and downloads EXT:rtehtmlarea if needed
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class RteHtmlAreaExtractionUpdate extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'rtehtmlarea',
|
||||
'RTE HTMLArea for TYPO3',
|
||||
'8.7.0',
|
||||
'friendsoftypo3/rtehtmlarea',
|
||||
'The extension provides the well-known RTE used in previous TYPO3 versions, if handling of images or custom legacy configurations are necessary.'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'You should install EXT:rtehtmlarea only if you really need it. ' . $this->extension->getDescription(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'rtehtmlareaExtension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install extension "rtehtmlarea" from TER';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The extension "rtehtmlarea" (RTE based on HtmlArea) was extracted into'
|
||||
. ' the TYPO3 Extension Repository. This update downloads the TYPO3 Extension from the TER.'
|
||||
. ' Use this if you have special configurations or image handling within Rich Text fields and uninstall the shipped EXT:rte_ckeditor.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !ExtensionManagementUtility::isLoaded('rtehtmlarea');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Migrate the field 'section_frame' for all content elements to 'frame_class'
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class SectionFrameToFrameClassUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'sectionFrameToFrameClassUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate the field "section_frame" for all content elements to "frame_class"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Section frames were used to control the wrapping of each content element in the frontend '
|
||||
. 'output, stored as integers in the database. To get rid of a nessesary mapping of those values we '
|
||||
. 'are now storing strings you can easily adjust that will simply passed to the rendering.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
|
||||
$tableColumns = $connection->getSchemaManager()->listTableColumns('tt_content');
|
||||
// Only proceed if section_frame field still exists
|
||||
if (!isset($tableColumns['section_frame'])) {
|
||||
return false;
|
||||
}
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$elementCount = $queryBuilder->count('uid')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->gt('section_frame', 0)
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
return (bool)$elementCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'section_frame')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->gt('section_frame', 0)
|
||||
)
|
||||
->execute();
|
||||
while ($record = $statement->fetch()) {
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->update('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('section_frame', 0, false)
|
||||
->set('frame_class', $this->mapSectionFrame($record['section_frame']));
|
||||
$queryBuilder->execute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the old to the new values
|
||||
*
|
||||
* @param int $sectionFrame The content of the FlexForm
|
||||
* @return string The equivalent value frame_class
|
||||
*/
|
||||
protected function mapSectionFrame($sectionFrame)
|
||||
{
|
||||
$mapping = [
|
||||
0 => 'default',
|
||||
5 => 'ruler-before',
|
||||
6 => 'ruler-after',
|
||||
10 => 'indent',
|
||||
11 => 'indent-left',
|
||||
12 => 'indent-right',
|
||||
66 => 'none'
|
||||
];
|
||||
if (array_key_exists($sectionFrame, $mapping)) {
|
||||
return $mapping[$sectionFrame];
|
||||
}
|
||||
return 'custom-' . (int)$sectionFrame;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Merge data stored in sys_log that belongs to sys_history
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class SeparateSysHistoryFromSysLogUpdate implements UpgradeWizardInterface, RepeatableInterface
|
||||
{
|
||||
|
||||
/** @var int Number of records to process in a single query to reduce memory footprint */
|
||||
private const BATCH_SIZE = 100;
|
||||
|
||||
/** @var int Phase that copies data from sys_log to sys_history */
|
||||
private const MOVE_DATA = 0;
|
||||
|
||||
/** @var int Phase that adds history records for inserts and deletes */
|
||||
private const UPDATE_HISTORY = 1;
|
||||
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'separateSysHistoryFromLog';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrates existing sys_log entries into sys_history';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The history of changes of a record is now solely stored within sys_history.'
|
||||
. ' Previous data within sys_log needs to be migrated into sys_history now.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (true) or not (false)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
// sys_log field has been removed, no need to do something.
|
||||
if (!$this->checkIfFieldInTableExists('sys_history', 'sys_log_uid')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there is data to migrate
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('sys_history');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$count = $queryBuilder->count('*')
|
||||
->from('sys_history')
|
||||
->where($queryBuilder->expr()->neq('sys_log_uid', 0))
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
|
||||
return $count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves data from sys_log into sys_history
|
||||
* where a reference is still there: sys_history.sys_log_uid > 0
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Doctrine\DBAL\ConnectionException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
// If rows from the target table that is updated and the sys_registry table are on the
|
||||
// same connection, the update statement and sys_registry position update will be
|
||||
// handled in a transaction to have an atomic operation in case of errors during execution.
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_history');
|
||||
$connectionForSysRegistry = $connectionPool->getConnectionForTable('sys_registry');
|
||||
|
||||
// In case the PHP ended for whatever reason, fetch the last position from registry
|
||||
// and only execute the phase(s) that has/have not been executed yet
|
||||
$startPositionAndPhase = $this->getStartPositionAndPhase();
|
||||
|
||||
if ($startPositionAndPhase['phase'] === self::MOVE_DATA) {
|
||||
$startPositionAndPhase = $this->moveDataFromSysLogToSysHistory(
|
||||
$connection,
|
||||
$connectionForSysRegistry,
|
||||
$startPositionAndPhase
|
||||
);
|
||||
}
|
||||
|
||||
if ($startPositionAndPhase['phase'] === self::UPDATE_HISTORY) {
|
||||
$this->keepHistoryForInsertAndDeleteActions(
|
||||
$connectionForSysRegistry,
|
||||
$startPositionAndPhase
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Core\Database\Connection $connection
|
||||
* @param \TYPO3\CMS\Core\Database\Connection $connectionForSysRegistry
|
||||
* @param array $startPositionAndPhase
|
||||
* @return array
|
||||
* @throws \Doctrine\DBAL\ConnectionException
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function moveDataFromSysLogToSysHistory(
|
||||
Connection $connection,
|
||||
Connection $connectionForSysRegistry,
|
||||
array $startPositionAndPhase
|
||||
): array {
|
||||
do {
|
||||
$processedRows = 0;
|
||||
|
||||
// update "modify" statements (= decoupling)
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$rows = $queryBuilder->select('sys_history.uid AS history_uid', 'sys_history.history_data', 'sys_log.*')
|
||||
->from('sys_history')
|
||||
->leftJoin(
|
||||
'sys_history',
|
||||
'sys_log',
|
||||
'sys_log',
|
||||
$queryBuilder->expr()->eq('sys_history.sys_log_uid', $queryBuilder->quoteIdentifier('sys_log.uid'))
|
||||
)
|
||||
->where($queryBuilder->expr()->gt('sys_history.uid', $queryBuilder->createNamedParameter($startPositionAndPhase['uid'])))
|
||||
->setMaxResults(self::BATCH_SIZE)
|
||||
->orderBy('sys_history.uid', 'ASC')
|
||||
->execute()
|
||||
->fetchAll();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$logData = $this->unserializeToArray((string)($row['log_data'] ?? ''));
|
||||
$historyData = $this->unserializeToArray((string)($row['history_data'] ?? ''));
|
||||
$updateData = [
|
||||
'actiontype' => RecordHistoryStore::ACTION_MODIFY,
|
||||
'usertype' => 'BE',
|
||||
'userid' => $row['userid'],
|
||||
'sys_log_uid' => 0,
|
||||
'history_data' => json_encode($historyData),
|
||||
'originaluserid' => empty($logData['originalUser']) ? null : $logData['originalUser']
|
||||
];
|
||||
|
||||
if ($connection === $connectionForSysRegistry) {
|
||||
// sys_history and sys_registry tables are on the same connection, use a transaction
|
||||
$connection->beginTransaction();
|
||||
try {
|
||||
$startPositionAndPhase = $this->updateTablesAndTrackProgress(
|
||||
$connection,
|
||||
$connection,
|
||||
$updateData,
|
||||
$logData,
|
||||
$row
|
||||
);
|
||||
$connection->commit();
|
||||
} catch (\Exception $up) {
|
||||
$connection->rollBack();
|
||||
throw ($up);
|
||||
}
|
||||
} else {
|
||||
// Different connections for sys_history and sys_registry -> execute two
|
||||
// distinct queries and hope for the best.
|
||||
$startPositionAndPhase = $this->updateTablesAndTrackProgress(
|
||||
$connection,
|
||||
$connectionForSysRegistry,
|
||||
$updateData,
|
||||
$logData,
|
||||
$row
|
||||
);
|
||||
}
|
||||
|
||||
$processedRows++;
|
||||
}
|
||||
// repeat until a resultset smaller than the batch size was processed
|
||||
} while ($processedRows === self::BATCH_SIZE);
|
||||
|
||||
// phase 0 is finished
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$startPositionAndPhase = [
|
||||
'phase' => self::UPDATE_HISTORY,
|
||||
'uid' => 0,
|
||||
];
|
||||
$registry->set('installSeparateHistoryFromSysLog', 'phaseAndPosition', $startPositionAndPhase);
|
||||
|
||||
return $startPositionAndPhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sys_history and sys_log tables
|
||||
*
|
||||
* Also keep track of progress in sys_registry
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Database\Connection $connection
|
||||
* @param \TYPO3\CMS\Core\Database\Connection $connectionForSysRegistry
|
||||
* @param array $updateData
|
||||
* @param array $logData
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
protected function updateTablesAndTrackProgress(
|
||||
Connection $connection,
|
||||
Connection $connectionForSysRegistry,
|
||||
array $updateData,
|
||||
array $logData,
|
||||
array $row
|
||||
): array {
|
||||
$connection->update(
|
||||
'sys_history',
|
||||
$updateData,
|
||||
['uid' => (int)$row['history_uid']],
|
||||
['uid' => Connection::PARAM_INT]
|
||||
);
|
||||
|
||||
// Store information about history entry in sys_log table
|
||||
$logData['history'] = $row['history_uid'];
|
||||
$connection->update(
|
||||
'sys_log',
|
||||
['log_data' => serialize($logData)],
|
||||
['uid' => (int)$row['uid']],
|
||||
['uid' => Connection::PARAM_INT]
|
||||
);
|
||||
$startPositionAndPhase = [
|
||||
'phase' => self::MOVE_DATA,
|
||||
'uid' => $row['history_uid'],
|
||||
];
|
||||
$connectionForSysRegistry->update(
|
||||
'sys_registry',
|
||||
[
|
||||
'entry_value' => serialize($startPositionAndPhase)
|
||||
],
|
||||
[
|
||||
'entry_namespace' => 'installSeparateHistoryFromSysLog',
|
||||
'entry_key' => 'phaseAndPosition',
|
||||
]
|
||||
);
|
||||
|
||||
return $startPositionAndPhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Insert and Delete actions from sys_log to sys_history
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Database\Connection $connectionForSysRegistry
|
||||
* @param array $startPositionAndPhase
|
||||
*/
|
||||
protected function keepHistoryForInsertAndDeleteActions(
|
||||
Connection $connectionForSysRegistry,
|
||||
array $startPositionAndPhase
|
||||
) {
|
||||
do {
|
||||
$processedRows = 0;
|
||||
|
||||
// Add insert/delete calls
|
||||
$logQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
|
||||
$result = $logQueryBuilder->select('uid', 'userid', 'action', 'tstamp', 'log_data', 'tablename', 'recuid')
|
||||
->from('sys_log')
|
||||
->where(
|
||||
$logQueryBuilder->expr()->eq('type', $logQueryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
|
||||
$logQueryBuilder->expr()->orX(
|
||||
$logQueryBuilder->expr()->eq('action', $logQueryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
|
||||
$logQueryBuilder->expr()->eq('action', $logQueryBuilder->createNamedParameter(3, \PDO::PARAM_INT))
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
$logQueryBuilder->expr()->gt('uid', $logQueryBuilder->createNamedParameter($startPositionAndPhase['uid']))
|
||||
)
|
||||
->orderBy('uid', 'ASC')
|
||||
->setMaxResults(self::BATCH_SIZE)
|
||||
->execute();
|
||||
|
||||
foreach ($result as $row) {
|
||||
$logData = $this->unserializeToArray((string)($row['log_data'] ?? ''));
|
||||
$store = GeneralUtility::makeInstance(
|
||||
RecordHistoryStore::class,
|
||||
RecordHistoryStore::USER_BACKEND,
|
||||
$row['userid'],
|
||||
(empty($logData['originalUser']) ? null : $logData['originalUser']),
|
||||
$row['tstamp']
|
||||
);
|
||||
|
||||
switch ($row['action']) {
|
||||
// Insert
|
||||
case 1:
|
||||
$store->addRecord($row['tablename'], (int)$row['recuid'], $logData);
|
||||
break;
|
||||
// Delete
|
||||
case 3:
|
||||
$store->deleteRecord($row['tablename'], (int)$row['recuid']);
|
||||
break;
|
||||
}
|
||||
|
||||
$startPositionAndPhase = [
|
||||
'phase' => self::UPDATE_HISTORY,
|
||||
'uid' => $row['uid'],
|
||||
];
|
||||
$connectionForSysRegistry->update(
|
||||
'sys_registry',
|
||||
[
|
||||
'entry_value' => serialize($startPositionAndPhase)
|
||||
],
|
||||
[
|
||||
'entry_namespace' => 'installSeparateHistoryFromSysLog',
|
||||
'entry_key' => 'phaseAndPosition',
|
||||
]
|
||||
);
|
||||
|
||||
$processedRows++;
|
||||
}
|
||||
// repeat until a result set smaller than the batch size was processed
|
||||
} while ($processedRows === self::BATCH_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given field /column in a table exists
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $fieldName
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkIfFieldInTableExists($table, $fieldName): bool
|
||||
{
|
||||
$tableColumns = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable($table)
|
||||
->getSchemaManager()
|
||||
->listTableColumns($table);
|
||||
return isset($tableColumns[$fieldName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with phase / uid combination that specifies the start position the
|
||||
* update process should start with.
|
||||
*
|
||||
* @return array New start position
|
||||
*/
|
||||
protected function getStartPositionAndPhase(): array
|
||||
{
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$startPosition = $registry->get('installSeparateHistoryFromSysLog', 'phaseAndPosition', []);
|
||||
if (empty($startPosition)) {
|
||||
$startPosition = [
|
||||
'phase' => self::MOVE_DATA,
|
||||
'uid' => 0,
|
||||
];
|
||||
$registry->set('installSeparateHistoryFromSysLog', 'phaseAndPosition', $startPosition);
|
||||
}
|
||||
|
||||
return $startPosition;
|
||||
}
|
||||
|
||||
protected function unserializeToArray(string $serialized): array
|
||||
{
|
||||
$unserialized = unserialize($serialized, ['allowed_classes' => false]);
|
||||
return is_array($unserialized) ? $unserialized : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Split menu types into dedicated content elements
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class SplitMenusUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'splitMenusUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Split menu types into dedicated content elements';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Menus have been split into dedicated content elements to provide '
|
||||
. 'a better maintainability and more easy to adjustable template with single '
|
||||
. 'responsibility for the rendering.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
|
||||
$tableColumns = $connection->getSchemaManager()->listTableColumns('tt_content');
|
||||
// Only proceed if menu_type field still exists
|
||||
if (!isset($tableColumns['menu_type'])) {
|
||||
return false;
|
||||
}
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$elementCount = $queryBuilder->count('uid')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('menu', \PDO::PARAM_STR))
|
||||
)
|
||||
->execute()->fetchColumn(0);
|
||||
return (bool)$elementCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'header', 'menu_type')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'CType',
|
||||
$queryBuilder->createNamedParameter('menu', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
->execute();
|
||||
while ($record = $statement->fetch()) {
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->update('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('CType', $this->mapMenuTypes($record['menu_type']));
|
||||
$queryBuilder->execute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the old to the new values
|
||||
*
|
||||
* @param string $menuType The content of the FlexForm
|
||||
* @return string The equivalent CType
|
||||
*/
|
||||
protected function mapMenuTypes($menuType)
|
||||
{
|
||||
$mapping = [
|
||||
0 => 'menu_pages',
|
||||
1 => 'menu_subpages',
|
||||
2 => 'menu_sitemap',
|
||||
3 => 'menu_section',
|
||||
4 => 'menu_abstract',
|
||||
5 => 'menu_recently_updated',
|
||||
6 => 'menu_related_pages',
|
||||
7 => 'menu_section_pages',
|
||||
8 => 'menu_sitemap_pages',
|
||||
'categorized_pages' => 'menu_categorized_pages',
|
||||
'categorized_content' => 'menu_categorized_content'
|
||||
];
|
||||
if (array_key_exists($menuType, $mapping)) {
|
||||
return $mapping[$menuType];
|
||||
}
|
||||
return 'menu_' . $menuType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Update backend user setting startModule if set to "help_aboutmodules"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class StartModuleUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'startModuleUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Update backend user setting "startModule"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The backend user setting startModule is changed for the extensions about/aboutmodules.'
|
||||
. ' Update all backend users that use ext:aboutmodules as startModule.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'uc')->from('be_users')->execute();
|
||||
$needsExecution = false;
|
||||
while ($backendUser = $statement->fetch()) {
|
||||
if ($backendUser['uc'] !== null) {
|
||||
$userConfig = unserialize($backendUser['uc'], ['allowed_classes' => false]);
|
||||
if ($userConfig['startModule'] === 'help_aboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutmodulesAboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutAboutmodules'
|
||||
) {
|
||||
$needsExecution = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $needsExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update if backend user's startmodule is
|
||||
* "help_aboutmodules" or "help_AboutmodulesAboutmodules"
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'uc')->from('be_users')->execute();
|
||||
while ($backendUser = $statement->fetch()) {
|
||||
if ($backendUser['uc'] !== null) {
|
||||
$userConfig = unserialize($backendUser['uc'], ['allowed_classes' => false]);
|
||||
if ($userConfig['startModule'] === 'help_aboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutmodulesAboutmodules'
|
||||
|| $userConfig['startModule'] === 'help_AboutAboutmodules'
|
||||
) {
|
||||
$userConfig['startModule'] = 'help_AboutAbout';
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->update('be_users')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($backendUser['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('uc', $queryBuilder->createNamedParameter(serialize($userConfig), \PDO::PARAM_LOB))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
|
||||
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
||||
use TYPO3\CMS\Core\Resource\Security\SvgSanitizer;
|
||||
use TYPO3\CMS\Core\Resource\StorageRepository;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
class SvgFilesSanitization implements UpgradeWizardInterface, ConfirmableInterface
|
||||
{
|
||||
/**
|
||||
* @var StorageRepository
|
||||
*/
|
||||
protected $storageRepository;
|
||||
|
||||
/**
|
||||
* @var Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
|
||||
$this->confirmation = new Confirmation(
|
||||
'Continue sanitizing SVG files?',
|
||||
$this->getDescription(),
|
||||
false,
|
||||
'sanitize, backup available',
|
||||
'cancel',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'svgFilesSanitization';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Sanitize existing SVG files in fileadmin folder';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'This upgrade wizard will sanitize all SVG files located in local file storages. '
|
||||
. 'It is very likely that file contents will be changed.' . "\n"
|
||||
. 'Before continuing, please ensure a proper backup of *.svg and *.svgz files is in place before continuing.';
|
||||
}
|
||||
|
||||
/**
|
||||
* To avoid timeout issues, no check is performed in advance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the update
|
||||
*
|
||||
* Called when a wizard reports that an update is necessary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
return $this->processSvgFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResourceStorage[]
|
||||
*/
|
||||
protected function resolveLocalStorages(): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->storageRepository->findByStorageType('Local'),
|
||||
function (ResourceStorage $storage) {
|
||||
return $storage->isWritable();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResourceStorage $storage
|
||||
* @return File[]
|
||||
* @throws InsufficientFolderAccessPermissionsException
|
||||
*/
|
||||
protected function resolveSvgFiles(ResourceStorage $storage): array
|
||||
{
|
||||
$filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
|
||||
$filter->setAllowedFileExtensions(['svg', 'svgz']);
|
||||
$files = $storage
|
||||
->setFileAndFolderNameFilters([
|
||||
[$filter, 'filterFileList']
|
||||
])
|
||||
->getFilesInFolder(
|
||||
$storage->getRootLevelFolder(),
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
true
|
||||
);
|
||||
$storage->resetFileAndFolderNameFiltersToDefault();
|
||||
return $files;
|
||||
}
|
||||
|
||||
protected function processSvgFiles(): bool
|
||||
{
|
||||
$successful = true;
|
||||
$sanitizer = GeneralUtility::makeInstance(SvgSanitizer::class);
|
||||
foreach ($this->resolveLocalStorages() as $storage) {
|
||||
try {
|
||||
$svgFiles = $this->resolveSvgFiles($storage);
|
||||
} catch (InsufficientFolderAccessPermissionsException $exception) {
|
||||
// @todo Add notice/warning for this upgrade process
|
||||
$successful = false;
|
||||
continue;
|
||||
}
|
||||
foreach ($svgFiles as $svgFile) {
|
||||
$oldFileContent = $svgFile->getContents();
|
||||
$newFileContent = $sanitizer->sanitizeContent($oldFileContent);
|
||||
if ($oldFileContent !== $newFileContent) {
|
||||
$svgFile->setContents($newFileContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $successful;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
|
||||
/**
|
||||
* Installs and downloads EXT:typo3db_legacy
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class Typo3DbExtractionUpdate extends AbstractDownloadExtensionUpdate
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
protected $confirmation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->extension = new ExtensionModel(
|
||||
'typo3db_legacy',
|
||||
'$GLOBALS[\'TYPO3_DB\'] compatibility layer',
|
||||
'1.1.1',
|
||||
'friendsoftypo3/typo3db-legacy',
|
||||
'This extension provides the well-known database API $GLOBALS[\'TYPO3_DB\'] used in previous TYPO3 versions for extensions that still rely on it.'
|
||||
);
|
||||
|
||||
$this->confirmation = new Confirmation(
|
||||
'Are you sure?',
|
||||
'You should install EXT:typo3db_legacy only if you really need it. ' . $this->extension->getDescription(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a confirmation message instance
|
||||
*
|
||||
* @return \TYPO3\CMS\Install\Updates\Confirmation
|
||||
*/
|
||||
public function getConfirmation(): Confirmation
|
||||
{
|
||||
return $this->confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 'typo3DbLegacyExtension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Install extension "typo3db_legacy" from TER';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'The old database API populated as $GLOBALS[\'TYPO3_DB\'] has been extracted into'
|
||||
. ' the TYPO3 Extension Repository. This update downloads the TYPO3 extension typo3db_legacy from the TER.'
|
||||
. ' Use this if you\'re dealing with extensions in the instance that still rely on the old database API.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an update necessary?
|
||||
* Is used to determine whether a wizard needs to be run.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
return !ExtensionManagementUtility::isLoaded($this->extension->getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface UpgradeWizardInterface
|
||||
*/
|
||||
interface UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* 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 the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string;
|
||||
|
||||
/**
|
||||
* Return the description for this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string;
|
||||
|
||||
/**
|
||||
* Execute the update
|
||||
*
|
||||
* Called when a wizard reports that an update is necessary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Migrate upload content element rendering from layout to uploads_type
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class UploadContentElementUpdate implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'uploadContentElementUpdate';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Migrate upload content element rendering from layout to uploads_type';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Rendering type field has been streamlined with fluid_styled_content.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$elementCount = $queryBuilder->count('uid')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('uploads', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->in('layout', [1, 2])
|
||||
)
|
||||
->execute()
|
||||
->fetchColumn(0);
|
||||
return (bool)$elementCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the database update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$statement = $queryBuilder->select('uid', 'layout')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('uploads', \PDO::PARAM_STR)),
|
||||
$queryBuilder->expr()->in('layout', [1, 2])
|
||||
)
|
||||
->execute();
|
||||
while ($record = $statement->fetch()) {
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->update('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->set('layout', 0, false)
|
||||
->set('uploads_type', $record['layout'])
|
||||
->execute();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Install\Updates;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Move "wizard done" flags to system registry
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class WizardDoneToRegistry implements UpgradeWizardInterface
|
||||
{
|
||||
/**
|
||||
* @return string Unique identifier of this updater
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'wizardDoneToRegistry';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Title of this updater
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Move "wizard done" flags from LocalConfiguration.php to system registry';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Moves all "wizard done" flags from LocalConfiguration.php to system registry.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an update is needed
|
||||
*
|
||||
* @return bool Whether an update is needed (TRUE) or not (FALSE)
|
||||
*/
|
||||
public function updateNecessary(): bool
|
||||
{
|
||||
$result = false;
|
||||
try {
|
||||
$wizardsDone = GeneralUtility::makeInstance(ConfigurationManager::class)
|
||||
->getLocalConfigurationValueByPath('INSTALL/wizardDone');
|
||||
if (!empty($wizardsDone)) {
|
||||
$result = true;
|
||||
}
|
||||
} catch (MissingArrayPathException $e) {
|
||||
// Result stays false with broken path
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] All new fields and tables must exist
|
||||
*/
|
||||
public function getPrerequisites(): array
|
||||
{
|
||||
return [
|
||||
DatabaseUpdatedPrerequisite::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the accordant updates.
|
||||
*
|
||||
* @return bool Whether everything went smoothly or not
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class);
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$wizardsDone = $configurationManager->getLocalConfigurationValueByPath('INSTALL/wizardDone');
|
||||
$configurationKeysToRemove = [];
|
||||
foreach ($wizardsDone as $wizardClassName => $value) {
|
||||
$registry->set('installUpdate', $wizardClassName, $value);
|
||||
$configurationKeysToRemove[] = 'INSTALL/wizardDone/' . $wizardClassName;
|
||||
}
|
||||
$configurationKeysToRemove[] = 'INSTALL/wizardDone';
|
||||
$configurationManager->removeLocalConfigurationKeysByPath($configurationKeysToRemove);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user