Initial commit - Typo3 11.5.41
This commit is contained in:
374
typo3conf/ext/ods_osm/Classes/Updates/FileLocationUpdater.php
Normal file
374
typo3conf/ext/ods_osm/Classes/Updates/FileLocationUpdater.php
Normal file
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2020 Alexander Bigga <alexander@bigga.de>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project 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.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script 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.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
namespace Bobosch\OdsOsm\Updates;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
|
||||
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
|
||||
use TYPO3\CMS\Install\Updates\ChattyInterface;
|
||||
|
||||
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\Connection;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Migrate location of marker and track files to new, FAL-based location
|
||||
*/
|
||||
class FileLocationUpdater implements UpgradeWizardInterface, ChattyInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var ResourceStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Array with table and fields to migrate
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldsToMigrate = [
|
||||
'tx_odsosm_marker' => 'icon',
|
||||
'tx_odsosm_track' => 'file'
|
||||
];
|
||||
|
||||
/**
|
||||
* the source file resides here
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $sourcePath = 'uploads/tx_odsosm/';
|
||||
|
||||
/**
|
||||
* target folder after migration
|
||||
* Relative to fileadmin
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $targetPath = '_migrated/tx_odsosm/';
|
||||
|
||||
/**
|
||||
* Return the identifier for this wizard
|
||||
* This must be the same string as used in the ext_localconf class registration
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'odsOsmFileLocationUpdater';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the speaking name of this wizard
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'EXT:ods_osm: Migrate used files to FAL';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description
|
||||
*
|
||||
* @return string Longer description of this updater
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Move marker images and track files of EXT:ods_osm to fileadmin/_migrated/tx_odsosm/ and convert reference in records.';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$numRecords = $this->falGetRecordsFromTable(true);
|
||||
if ($numRecords > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the update
|
||||
*
|
||||
* Called when a wizard reports that an update is necessary
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function executeUpdate(): bool
|
||||
{
|
||||
$result = true;
|
||||
try {
|
||||
$numRecords = $this->falGetRecordsFromTable(true);
|
||||
if ($numRecords > 0) {
|
||||
$this->falPerformUpdate();
|
||||
}
|
||||
} 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)
|
||||
*
|
||||
* Work based on BackendLayoutIconUpdateWizard::class
|
||||
*
|
||||
* @return array|int
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function falGetRecordsFromTable($countOnly = false)
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$allResults = [];
|
||||
$numResults = 0;
|
||||
foreach(array_keys($this->fieldsToMigrate) as $table) {
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
try {
|
||||
$result = $queryBuilder
|
||||
->select('uid', 'pid', $this->fieldsToMigrate[$table])
|
||||
->from($table)
|
||||
->where(
|
||||
$queryBuilder->expr()->isNotNull($this->fieldsToMigrate[$table]),
|
||||
$queryBuilder->expr()->neq(
|
||||
$this->fieldsToMigrate[$table],
|
||||
$queryBuilder->createNamedParameter('', Connection::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->comparison(
|
||||
'CAST(CAST(' . $queryBuilder->quoteIdentifier($this->fieldsToMigrate[$table]) . ' AS DECIMAL) AS CHAR)',
|
||||
ExpressionBuilder::NEQ,
|
||||
'CAST(' . $queryBuilder->quoteIdentifier($this->fieldsToMigrate[$table]) . ' AS CHAR)'
|
||||
)
|
||||
)
|
||||
->orderBy('uid')
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
if ($countOnly === true) {
|
||||
$numResults += count($result);
|
||||
} else {
|
||||
$allResults[$table] = $result;
|
||||
}
|
||||
} catch (DBALException $e) {
|
||||
throw new \RuntimeException(
|
||||
'Database query failed. Error was: ' . $e->getPrevious()->getMessage(),
|
||||
1511950673
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($countOnly === true) {
|
||||
return $numResults;
|
||||
}
|
||||
return $allResults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs the database update.
|
||||
*
|
||||
* @return bool TRUE on success, FALSE on error
|
||||
*/
|
||||
protected function falPerformUpdate(): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
try {
|
||||
$storages = GeneralUtility::makeInstance(StorageRepository::class)->findAll();
|
||||
$this->storage = $storages[0];
|
||||
|
||||
$records = $this->falGetRecordsFromTable();
|
||||
foreach ($records as $table => $recordsInTable) {
|
||||
foreach ($recordsInTable as $record) {
|
||||
$this->migrateField($table, $record);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$result = false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates a single field.
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $row
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function migrateField($table, $row)
|
||||
{
|
||||
$fieldItem = trim($row[$this->fieldsToMigrate[$table]]);
|
||||
|
||||
if (empty($fieldItem) || is_numeric($fieldItem)) {
|
||||
return;
|
||||
}
|
||||
$fileadminDirectory = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/';
|
||||
$i = 0;
|
||||
|
||||
$storageUid = (int)$this->storage->getUid();
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
|
||||
$fileUid = null;
|
||||
$sourcePath = Environment::getPublicPath() . '/' . $this->sourcePath . $fieldItem;
|
||||
$targetDirectory = Environment::getPublicPath() . '/' . $fileadminDirectory . $this->targetPath;
|
||||
$targetPath = $targetDirectory . basename($fieldItem);
|
||||
|
||||
// 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');
|
||||
$existingFileRecord = $queryBuilder->select('uid')->from('sys_file')->where($queryBuilder->expr()->eq(
|
||||
'missing',
|
||||
$queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
|
||||
), $queryBuilder->expr()->eq(
|
||||
'sha1',
|
||||
$queryBuilder->createNamedParameter($fileSha1, Connection::PARAM_STR)
|
||||
), $queryBuilder->expr()->eq(
|
||||
'storage',
|
||||
$queryBuilder->createNamedParameter($storageUid, Connection::PARAM_INT)
|
||||
))->executeQuery()->fetchAssociative();
|
||||
|
||||
// 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 . $fieldItem);
|
||||
$fileUid = $file->getUid();
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// no file found, no reference can be set
|
||||
$this->logger->notice(
|
||||
'File ' . $this->sourcePath . $fieldItem . ' does not exist. Reference was not migrated.',
|
||||
[
|
||||
'table' => $table,
|
||||
'record' => $row,
|
||||
'field' => $fieldItem,
|
||||
]
|
||||
);
|
||||
$format = 'File \'%s\' does not exist. Referencing field: %s.%d.%s. The reference was not migrated.';
|
||||
$this->output->writeln(sprintf(
|
||||
$format,
|
||||
$this->sourcePath . $fieldItem,
|
||||
$table,
|
||||
$row['uid'],
|
||||
$fieldItem
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fileUid > 0) {
|
||||
$fields = [
|
||||
'fieldname' => $this->fieldsToMigrate[$table],
|
||||
'table_local' => 'sys_file',
|
||||
'pid' => ($table === 'pages' ? $row['uid'] : $row['pid']),
|
||||
'uid_foreign' => $row['uid'],
|
||||
'uid_local' => $fileUid,
|
||||
'tablenames' => $table,
|
||||
'crdate' => time(),
|
||||
'tstamp' => time(),
|
||||
'sorting_foreign' => $i,
|
||||
];
|
||||
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
|
||||
$queryBuilder->insert('sys_file_reference')->values($fields)->executeStatement();
|
||||
|
||||
++$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 === 1) {
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->update($table)->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($row['uid'], Connection::PARAM_INT)
|
||||
)
|
||||
)->set($this->fieldsToMigrate[$table], $i)->executeStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user