Commit 51cd1d8e authored by Dainis Abols's avatar Dainis Abols
Browse files

TPO3 v10 Compatability. Needs file migration on install!

v4.2.0
parent be17bdfa
...@@ -9,6 +9,7 @@ use Lu\LuNotices\Domain\Repository\TopicsRepository; ...@@ -9,6 +9,7 @@ use Lu\LuNotices\Domain\Repository\TopicsRepository;
use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
...@@ -208,31 +209,13 @@ class NoticesController extends ActionController ...@@ -208,31 +209,13 @@ class NoticesController extends ActionController
* *
* @return array * @return array
*/ */
private function getFilesByNotice($noticeId) private function getFilesByNotice($noticeId): array
{ {
// Pre-set values // Pre-set values
$files = []; $noticesTable = 'tx_lunotices_domain_model_notices';
$notice = $this->noticesRepository->findByUid($noticeId); $fileRepository = GeneralUtility::makeInstance(FileRepository::class);
// Check if files found
if (!empty($notice->getFiles())) {
$noticesFiles = explode(',', $notice->getFiles());
// Removes storage folder name
if (!empty($noticesFiles)) {
foreach ($noticesFiles as $filePath) {
if (!empty($filePath) && file_exists(Environment::getPublicPath().'/'.$filePath)) {
$resourceFactory = ResourceFactory::getInstance();
$filePath = str_replace('fileadmin/', '', $filePath);
$f = $resourceFactory->getFileObjectByStorageAndIdentifier(1, $filePath);
$f->getProperties();
$files[] = $f;
}
}
}
}
return $files; return $fileRepository->findByRelation($noticesTable, 'media', $noticeId);
} }
/** /**
......
<?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 Lu\LuNotices\Updates;
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;
use TYPO3\CMS\Install\Updates\ChattyInterface;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
/**
* Upgrade wizard which goes through all files
* and creates sys_file records as well as sys_file_reference records for each hit.
*/
class FileMigrationWizard implements UpgradeWizardInterface, ChattyInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @var OutputInterface
*/
protected $output;
/**
* @var ResourceStorage
*/
protected $storage;
/**
* Table to migrate records from
*
* @var string
*/
protected $table = 'tx_lunotices_domain_model_notices';
/**
* Table field holding the migration to be
*
* @var string
*/
protected $fieldToMigrate = 'files';
/**
* target folder after migration
* Relative to fileadmin
*
* @var string
*/
protected $targetPath = '_migrated/';
/**
* @return string Unique identifier of this updater
*/
public function getIdentifier(): string
{
return 'noticesFileMigration';
}
/**
* @return string Title of this updater
*/
public function getTitle(): string
{
return 'Migrate all LU Notices file relations 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'
. ' notices.files 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() . '/' . $item;
$strippedPath = str_replace("fileadmin/user_upload/", "", $item);
$targetDirectory = Environment::getPublicPath() . '/' . $fileadminDirectory . $this->targetPath;
$targetPath = $targetDirectory . $strippedPath;
// Remove file name
$temp = explode("/", $strippedPath);
array_pop($temp);
$subdir = implode("/", $temp);
$subdir = $targetDirectory . $subdir;
// maybe the file was already moved, so check if the original file still exists
if (file_exists($sourcePath) && !is_dir($sourcePath)) {
if (!is_dir($targetDirectory)) {
GeneralUtility::mkdir_deep($targetDirectory);
}
// Create subdirectory
if (!is_dir($subdir)) {
GeneralUtility::mkdir_deep($subdir);
}
// 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 . $strippedPath);
$fileUid = $file->getUid();
} catch (\InvalidArgumentException $e) {
// no file found, no reference can be set
$this->logger->notice(
'File ' . $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,
$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_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();
}
}
}
...@@ -32,25 +32,27 @@ return [ ...@@ -32,25 +32,27 @@ return [
'exclude' => 0, 'exclude' => 0,
'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:starttime', 'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:starttime',
'config' => [ 'config' => [
'type' => 'input', 'type' => 'input',
'size' => 8, 'renderType' => 'inputDateTime',
'max' => 20, 'size' => 8,
'eval' => 'datetime,int', 'max' => 20,
'default' => '0', 'eval' => 'datetime,int',
'checkbox' => '0', 'default' => '0',
'checkbox' => '0',
], ],
], ],
'endtime' => [ 'endtime' => [
'exclude' => 0, 'exclude' => 0,
'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:endtime', 'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:endtime',
'config' => [ 'config' => [
'type' => 'input', 'type' => 'input',
'size' => 8, 'renderType' => 'inputDateTime',
'max' => 20, 'size' => 8,
'eval' => 'datetime,int', 'max' => 20,
'checkbox' => '0', 'eval' => 'datetime,int',
'default' => '0', 'checkbox' => '0',
'range' => [ 'default' => '0',
'range' => [
'upper' => mktime(3, 14, 7, 1, 19, 2038), 'upper' => mktime(3, 14, 7, 1, 19, 2038),
'lower' => mktime(0, 0, 0, date('m') - 1, date('d'), date('Y')), 'lower' => mktime(0, 0, 0, date('m') - 1, date('d'), date('Y')),
], ],
...@@ -82,40 +84,26 @@ return [ ...@@ -82,40 +84,26 @@ return [
'eval' => 'trim', 'eval' => 'trim',
], ],
], ],
'body' => [ 'body' => [
'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:notices.body', 'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:notices.body',
'config' => [ 'config' => [
'type' => 'text', 'type' => 'text',
'cols' => 30, 'enableRichtext' => true,
'rows' => 5, 'cols' => 30,
'eval' => 'trim', 'rows' => 5,
'eval' => 'trim',
], ],
'defaultExtras' => 'richtext[]',
], ],
'url' => [ 'url' => [
'exclude' => 0, 'exclude' => 0,
'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:notice.url', 'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:notice.url',
'config' => [ 'config' => [
'type' => 'input', 'type' => 'input',
'size' => '15', 'renderType' => 'inputLink',
'max' => '255', 'size' => '15',
'checkbox' => '', 'max' => '255',
'eval' => 'trim', 'checkbox' => '',
'wizards' => [ 'eval' => 'trim',
'_PADDING' => 2,
'link' => [
'type' => 'popup',
'title' => 'Link',
'icon' => 'link_popup.gif',
'module' => [
'name' => 'wizard_element_browser',
'urlParameters' => [
'mode' => 'wizard',
],
],
'JSopenParams' => 'height=300,width=500,status=0,menubar=0,scrollbars=1',
],
],
], ],
], ],
'priority' => [ 'priority' => [
...@@ -137,18 +125,15 @@ return [ ...@@ -137,18 +125,15 @@ return [
'files' => [ 'files' => [
'exclude' => 0, 'exclude' => 0,
'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:notice.files', 'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:notice.files',
'config' => [ 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
'type' => 'group', 'files',
//'internal_type' => 'file', [
'internal_type' => 'file_reference', 'minitems' => 0,
'allowed' => '', 'maxitems' => 100,
'disallowed' => 'php,php3', ],
'max_size' => $GLOBALS['TYPO3_CONF_VARS']['BE']['maxFileSize'], '',
//'uploadfolder' => 'uploads/user_lunotices', 'php,php3'
'size' => 10, ),
'minitems' => 0,
'maxitems' => 100,
],
], ],
], ],
'types' => [ 'types' => [
......
...@@ -91,6 +91,7 @@ return [ ...@@ -91,6 +91,7 @@ return [
'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:fegroup', 'label' => 'LLL:EXT:lu_notices/Resources/Private/Language/locallang.xlf:fegroup',
'config' => [ 'config' => [
'type' => 'select', 'type' => 'select',
'renderType' => 'selectSingle',
'foreign_table' => 'fe_groups', 'foreign_table' => 'fe_groups',
'foreign_table_where' => 'ORDER BY fe_groups.title ASC', 'foreign_table_where' => 'ORDER BY fe_groups.title ASC',
'eval' => 'required', 'eval' => 'required',
...@@ -107,7 +108,7 @@ return [ ...@@ -107,7 +108,7 @@ return [
], ],
], ],
'types' => [ 'types' => [
'0' => ['showitem' => 'hidden;;1;;1-1-1, ;;2;;2-2-2, description;;;;3-3-3, priority'], '0' => ['showitem' => 'hidden, --palette--;;1;;1-1-1, --palette--;;2;;2-2-2, description;;;;3-3-3, priority'],
], ],
'palettes' => [ 'palettes' => [
'1' => ['showitem' => 'starttime, endtime, fe_group'], '1' => ['showitem' => 'starttime, endtime, fe_group'],
......
This diff is collapsed.
{ {
"name": "luitd/lu-notices", "name": "luitd/lu-notices",
"version": "4.1.6", "version": "4.2.0",
"description": "General notice publisher for internal portal", "description": "General notice publisher for internal portal",
"type": "typo3-cms-extension", "type": "typo3-cms-extension",
"keywords": [ "keywords": [
......
...@@ -9,10 +9,10 @@ $EM_CONF[$_EXTKEY] = [ ...@@ -9,10 +9,10 @@ $EM_CONF[$_EXTKEY] = [
'author_company' => 'University of Latvia', 'author_company' => 'University of Latvia',
'state' => 'stable', 'state' => 'stable',
'clearCacheOnLoad' => true, 'clearCacheOnLoad' => true,
'version' => '4.1.6', 'version' => '4.2.0',
'constraints' => [ 'constraints' => [
'depends' => [ 'depends' => [
'typo3' => '9.5.0-9.9.99', 'typo3' => '9.5.0-10.9.99',
], ],
], ],
'autoload' => [ 'autoload' => [
......
...@@ -25,3 +25,6 @@ defined('TYPO3_MODE') || die('Access denied.'); ...@@ -25,3 +25,6 @@ defined('TYPO3_MODE') || die('Access denied.');
// Register hooks // Register hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList::class]['modifyQuery'][] $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList::class]['modifyQuery'][]
= \Lu\LuNotices\Hooks\Backend\RecordListQueryHook::class; = \Lu\LuNotices\Hooks\Backend\RecordListQueryHook::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['noticesFileMigration']
= Lu\LuNotices\Updates\FileMigrationWizard::class;
...@@ -25,29 +25,30 @@ CREATE TABLE tx_lunotices_domain_model_topics ( ...@@ -25,29 +25,30 @@ CREATE TABLE tx_lunotices_domain_model_topics (
# #
# Table structure for table 'tx_lunotices_domain_model_notices' # Table structure for table 'tx_lunotices_domain_model_notices'
# #
CREATE TABLE tx_lunotices_domain_model_notices ( CREATE TABLE tx_lunotices_domain_model_notices
uid INT(11) NOT NULL auto_increment, (
pid INT(11) DEFAULT '0' NOT NULL, uid INT(11) NOT NULL auto_increment,
topic_id text, pid INT(11) DEFAULT '0' NOT NULL,
title tinytext, topic_id text,
summary text, title tinytext,
body longtext, summary text,
url tinytext,