Provide location update wizard

This commit is contained in:
Daniel Siepmann 2023-01-09 11:49:51 +00:00
parent ff8519e827
commit 8bd15ac380
4 changed files with 308 additions and 0 deletions

View file

@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
/*
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
namespace Wrm\Events\Updates;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
class MigrateOldLocations implements UpgradeWizardInterface
{
/**
* @var ConnectionPool
*/
private $connectionPool;
/**
* @var DataHandler
*/
private $dataHandler;
/**
* @var Logger
*/
private $logger;
/**
* @var array
*/
private $uidsForTranslation = [];
public function __construct(
ConnectionPool $connectionPool,
DataHandler $dataHandler,
LogManager $logManager
) {
$this->connectionPool = $connectionPool;
$this->dataHandler = $dataHandler;
$this->logger = $logManager->getLogger(self::class);
}
public function getTitle(): string
{
return 'Migrate EXT:event location data.';
}
public function getDescription(): string
{
return 'Checks for legacy location data stored within events and will create dedicated location records and relations.';
}
public function updateNecessary(): bool
{
return $this->getQueryBuilder()
->count('*')
->execute()
->fetchOne() > 0
;
}
public function executeUpdate(): bool
{
$result = $this->getQueryBuilder()->execute();
foreach ($result as $eventRecord) {
$this->logger->info('Updating event record.', ['record' => $eventRecord]);
$eventRecord['location'] = $this->getLocationUid($eventRecord);
$this->uidsForTranslation[$eventRecord['uid'] . '-' . $eventRecord['sys_language_uid']] = $eventRecord['location'];
$this->updateEvent($eventRecord);
}
return true;
}
private function getLocationUid(array $event): int
{
$existingUid = $this->getExitingLocationUid($event);
if ($existingUid > 0) {
$this->logger->info('Location already exists', ['uid' => $existingUid, 'event' => $event]);
return $existingUid;
}
return $this->createLocation($event);
}
private function getExitingLocationUid(array $event): int
{
$oldColumns = [
'sys_language_uid' => 'sys_language_uid',
'zzz_deleted_name' => 'name',
'zzz_deleted_street' => 'street',
'zzz_deleted_district' => 'district',
'zzz_deleted_city' => 'city',
'zzz_deleted_zip' => 'zip',
'zzz_deleted_country' => 'country',
'zzz_deleted_phone' => 'phone',
'zzz_deleted_latitude' => 'latitude',
'zzz_deleted_longitude' => 'longitude',
];
$qb = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_location');
$qb->select('uid', 'l10n_parent');
$qb->from('tx_events_domain_model_location');
foreach ($oldColumns as $oldName => $newName) {
$qb->andWhere($qb->expr()->eq($newName, $qb->createNamedParameter($event[$oldName])));
}
$uids = $qb->execute()->fetchAssociative();
if (is_bool($uids)) {
return 0;
}
return $uids['l10n_parent'] ?: $uids['uid'];
}
private function createLocation(array $event): int
{
$this->logger->info('Location will be created.', ['event' => $event]);
$record = [
'pid' => $event['pid'],
'sys_language_uid' => $event['sys_language_uid'],
'name' => $event['zzz_deleted_name'],
'street' => $event['zzz_deleted_street'],
'district' => $event['zzz_deleted_district'],
'city' => $event['zzz_deleted_city'],
'zip' => $event['zzz_deleted_zip'],
'country' => $event['zzz_deleted_country'],
'phone' => $event['zzz_deleted_phone'],
'latitude' => $event['zzz_deleted_latitude'],
'longitude' => $event['zzz_deleted_longitude'],
];
$recordUid = 'NEW12121';
$l10nParentUid = $this->uidsForTranslation[$event['l10n_parent'] . '-0'] ?? 0;
$dataHandler = clone $this->dataHandler;
if ($event['sys_language_uid'] > 0 && $l10nParentUid > 0) {
$this->logger->info('Foreign language, create translation.', [
'l10nParentUid' => $l10nParentUid,
'event' => $event,
]);
$dataHandler->start([], [
'tx_events_domain_model_location' => [
$l10nParentUid => [
'localize' => $event['sys_language_uid'],
],
],
]);
$dataHandler->process_cmdmap();
$recordUid = $dataHandler->copyMappingArray_merged['tx_events_domain_model_location'][$l10nParentUid] ?? 0;
}
$this->logger->info('Create or update loation.', [
'recordUid' => $recordUid,
'l10nParentUid' => $l10nParentUid,
'event' => $event,
'record' => $record,
]);
$dataHandler->start([
'tx_events_domain_model_location' => [
$recordUid => $record,
],
], []);
$dataHandler->process_datamap();
$uid = $dataHandler->substNEWwithIDs[$recordUid] ?? 0;
$this->logger->info('Created or updated location.', [
'uid' => $uid,
]);
if ($uid > 0) {
return $uid;
}
if ($l10nParentUid > 0) {
return $l10nParentUid;
}
throw new \Exception('Could not create location: ' . implode(', ', $dataHandler->errorLog), 1672916613);
}
private function updateEvent(array $event): void
{
$this->connectionPool
->getConnectionForTable('tx_events_domain_model_event')
->update(
'tx_events_domain_model_event',
['location' => $event['location']],
['uid' => $event['uid']]
)
;
}
private function getQueryBuilder(): QueryBuilder
{
$oldColumns = [
'zzz_deleted_name',
'zzz_deleted_street',
'zzz_deleted_district',
'zzz_deleted_city',
'zzz_deleted_zip',
'zzz_deleted_country',
'zzz_deleted_phone',
'zzz_deleted_latitude',
'zzz_deleted_longitude',
];
$qb = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_event');
$qb->getRestrictions()->removeAll();
$qb->select(...$oldColumns);
$qb->addSelect('uid', 'pid', 'sys_language_uid', 'l10n_parent');
$qb->from('tx_events_domain_model_event');
foreach ($oldColumns as $columnName) {
$qb->orWhere($qb->expr()->neq($columnName, $qb->createNamedParameter('')));
}
$qb->orderBy('sys_language_uid', 'ASC');
$qb->addOrderBy('l10n_parent', 'ASC');
$qb->addOrderBy('uid', 'ASC');
return $qb;
}
public function getIdentifier(): string
{
return self::class;
}
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class,
];
}
public static function register(): void
{
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][self::class] = self::class;
}
}

View file

@ -16,6 +16,16 @@ Features
* Pages can now define they store event records. * Pages can now define they store event records.
A new entry was added to select and the icon within page tree will be replaced. A new entry was added to select and the icon within page tree will be replaced.
* Provided update wizard to migrate old location data to new one.
It is important to execute the database compare once, so old columns are prefixed but not removed.
Execute wizard and you are done.
Locations will be extracted from old events and placed on same pid.
You might wanna move all locations to a dedicated new pid in the end, e.g. with an sql query (Replace 2 by your page uid):
.. code-block:: sql
UPDATE tx_events_domain_model_location SET pid = 2
Fixes Fixes
----- -----

View file

@ -50,4 +50,6 @@ call_user_func(function () {
\TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
['source' => 'EXT:events/Resources/Public/Icons/Folder.svg'] ['source' => 'EXT:events/Resources/Public/Icons/Folder.svg']
); );
\Wrm\Events\Updates\MigrateOldLocations::register();
}); });

View file

@ -4,10 +4,12 @@ parameters:
message: "#^Cannot call method typoLink_URL\\(\\) on TYPO3\\\\CMS\\\\Frontend\\\\ContentObject\\\\ContentObjectRenderer\\|null\\.$#" message: "#^Cannot call method typoLink_URL\\(\\) on TYPO3\\\\CMS\\\\Frontend\\\\ContentObject\\\\ContentObjectRenderer\\|null\\.$#"
count: 1 count: 1
path: Classes/Controller/DateController.php path: Classes/Controller/DateController.php
- -
message: "#^Parameter \\#1 \\$categories of method Wrm\\\\Events\\\\Domain\\\\Model\\\\Event\\:\\:setCategories\\(\\) expects TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<Wrm\\\\Events\\\\Domain\\\\Model\\\\Category\\>, TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<mixed\\> given\\.$#" message: "#^Parameter \\#1 \\$categories of method Wrm\\\\Events\\\\Domain\\\\Model\\\\Event\\:\\:setCategories\\(\\) expects TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<Wrm\\\\Events\\\\Domain\\\\Model\\\\Category\\>, TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage\\<mixed\\> given\\.$#"
count: 1 count: 1
path: Classes/Service/DestinationDataImportService.php path: Classes/Service/DestinationDataImportService.php
- -
message: "#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#" message: "#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#"
count: 1 count: 1
@ -38,3 +40,38 @@ parameters:
count: 2 count: 2
path: Classes/Service/DestinationDataImportService/CategoriesAssignment.php path: Classes/Service/DestinationDataImportService/CategoriesAssignment.php
-
message: "#^Argument of an invalid type Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int supplied for foreach, only iterables are supported\\.$#"
count: 1
path: Classes/Updates/MigrateOldLocations.php
-
message: "#^Cannot call method fetchAssociative\\(\\) on Doctrine\\\\DBAL\\\\Driver\\\\ResultStatement\\|int\\.$#"
count: 1
path: Classes/Updates/MigrateOldLocations.php
-
message: "#^Argument of an invalid type Doctrine\\\\DBAL\\\\Result\\|int supplied for foreach, only iterables are supported\\.$#"
count: 1
path: Classes/Updates/MigrateOldLocations.php
-
message: "#^Cannot call method fetchAssociative\\(\\) on Doctrine\\\\DBAL\\\\Result\\|int\\.$#"
count: 1
path: Classes/Updates/MigrateOldLocations.php
-
message: "#^Method Wrm\\\\Events\\\\Updates\\\\MigrateOldLocations\\:\\:getExitingLocationUid\\(\\) should return int but returns mixed\\.$#"
count: 1
path: Classes/Updates/MigrateOldLocations.php
-
message: "#^Offset 'sys_language_uid' does not exist on array\\{location\\: int\\}\\.$#"
count: 1
path: Classes/Updates/MigrateOldLocations.php
-
message: "#^Offset 'uid' does not exist on array\\{location\\: int\\}\\.$#"
count: 1
path: Classes/Updates/MigrateOldLocations.php