WIP|Add TYPO3 13 LTS Support

Relates: #11322
This commit is contained in:
Daniel Siepmann 2024-08-19 15:22:56 +02:00 committed by Daniel Siepmann
parent 60b752e4b2
commit c8a17c6fa2
SSH key fingerprint: SHA256:nAjx3Dpp8kuAC+S7QXj8BWmqw+KI1Miu+5e40BP3LXA
62 changed files with 667 additions and 405 deletions

View file

@ -90,6 +90,10 @@ jobs:
typo3-version: '^12.4' typo3-version: '^12.4'
- php-version: '8.3' - php-version: '8.3'
typo3-version: '^12.4' typo3-version: '^12.4'
- php-version: '8.2'
typo3-version: '^13.4'
- php-version: '8.3'
typo3-version: '^13.4'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -119,6 +123,10 @@ jobs:
typo3-version: '^12.4' typo3-version: '^12.4'
- php-version: '8.3' - php-version: '8.3'
typo3-version: '^12.4' typo3-version: '^12.4'
- php-version: '8.2'
typo3-version: '^13.4'
- php-version: '8.3'
typo3-version: '^13.4'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View file

@ -26,7 +26,6 @@ namespace WerkraumMedia\Events\Caching;
use DateTime; use DateTime;
use DateTimeImmutable; use DateTimeImmutable;
use InvalidArgumentException; use InvalidArgumentException;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\SingletonInterface;
@ -44,13 +43,10 @@ class PageCacheTimeout implements SingletonInterface
{ {
private ?DateTimeImmutable $timeout = null; private ?DateTimeImmutable $timeout = null;
private FrontendInterface $runtimeCache;
public function __construct( public function __construct(
CacheManager $cacheManager, private readonly FrontendInterface $runtimeCache,
private readonly Context $context private readonly Context $context,
) { ) {
$this->runtimeCache = $cacheManager->getCache('runtime');
} }
public function modifyCacheLifetimeForPage(ModifyCacheLifetimeForPageEvent $event): void public function modifyCacheLifetimeForPage(ModifyCacheLifetimeForPageEvent $event): void

View file

@ -25,13 +25,14 @@ namespace WerkraumMedia\Events\Controller;
use TYPO3\CMS\Core\Http\PropagateResponseException; use TYPO3\CMS\Core\Http\PropagateResponseException;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\View\ViewInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\ErrorController; use TYPO3\CMS\Frontend\Controller\ErrorController;
use TYPO3Fluid\Fluid\View\ViewInterface; use TYPO3Fluid\Fluid\View\ViewInterface as FluidStandaloneViewInterface;
use WerkraumMedia\Events\Caching\CacheManager; use WerkraumMedia\Events\Caching\CacheManager;
class AbstractController extends ActionController abstract class AbstractController extends ActionController
{ {
/** /**
* @var CacheManager * @var CacheManager
@ -60,17 +61,13 @@ class AbstractController extends ActionController
/** /**
* Extend original to also add data from current cobject if available. * Extend original to also add data from current cobject if available.
*/ */
protected function resolveView(): ViewInterface protected function initializeView(ViewInterface|FluidStandaloneViewInterface $view): void
{ {
$view = parent::resolveView();
$view->assign('data', []); $view->assign('data', []);
$cObject = $this->request->getAttribute('currentContentObject'); $cObject = $this->request->getAttribute('currentContentObject');
if ($cObject instanceof ContentObjectRenderer && is_array($cObject->data)) { if ($cObject instanceof ContentObjectRenderer && is_array($cObject->data)) {
$view->assign('data', $cObject->data); $view->assign('data', $cObject->data);
} }
return $view;
} }
protected function trigger404(string $message): void protected function trigger404(string $message): void

View file

@ -5,13 +5,13 @@ declare(strict_types=1);
namespace WerkraumMedia\Events\Domain\DestinationData; namespace WerkraumMedia\Events\Domain\DestinationData;
use Exception; use Exception;
use PDO;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
use TYPO3\CMS\Extbase\Persistence\Generic\Session; use TYPO3\CMS\Extbase\Persistence\Generic\Session;
use WerkraumMedia\Events\Domain\Model\Import; use WerkraumMedia\Events\Domain\Model\Import;
use WerkraumMedia\Events\Service\ExtbaseConfigurationManagerService;
final class ImportFactory final class ImportFactory
{ {
@ -24,7 +24,8 @@ final class ImportFactory
private readonly ConnectionPool $connectionPool, private readonly ConnectionPool $connectionPool,
private readonly Session $extbasePersistenceSession, private readonly Session $extbasePersistenceSession,
private readonly DataMapper $dataMapper, private readonly DataMapper $dataMapper,
private readonly ResourceFactory $resourceFactory private readonly ResourceFactory $resourceFactory,
private readonly ExtbaseConfigurationManagerService $extbaseConfigurationManagerService,
) { ) {
} }
@ -49,14 +50,14 @@ final class ImportFactory
$qb = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_import'); $qb = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_import');
$qb->select('*'); $qb->select('*');
$qb->from('tx_events_domain_model_import'); $qb->from('tx_events_domain_model_import');
$qb->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid, PDO::PARAM_INT))); $qb->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
$result = $qb->executeQuery()->fetch(); $result = $qb->executeQuery()->fetchAssociative();
if (is_array($result) === false) { if (is_array($result) === false) {
throw new Exception('Could not fetch import record with uid "' . $uid . '".', 1643267492); throw new Exception('Could not fetch import record with uid "' . $uid . '".', 1643267492);
} }
$result = array_map('strval', $result); $result = array_map(strval(...), $result);
return $result; return $result;
} }
@ -67,13 +68,13 @@ final class ImportFactory
$qb->select('*'); $qb->select('*');
$qb->from('tx_events_domain_model_import'); $qb->from('tx_events_domain_model_import');
$result = $qb->executeQuery()->fetchAll(); $result = $qb->executeQuery()->fetchAllAssociative();
if (count($result) === 0) { if (count($result) === 0) {
throw new Exception('Could not fetch any import record.', 1643267492); throw new Exception('Could not fetch any import record.', 1643267492);
} }
foreach ($result as $key => $entry) { foreach ($result as $key => $entry) {
$result[$key] = array_map('strval', $entry); $result[$key] = array_map(strval(...), $entry);
} }
return $result; return $result;
@ -94,6 +95,7 @@ final class ImportFactory
{ {
$this->folderInstance = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($data['files_folder']); $this->folderInstance = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($data['files_folder']);
$this->extbasePersistenceSession->registerObject($this->folderInstance, $data['files_folder']); $this->extbasePersistenceSession->registerObject($this->folderInstance, $data['files_folder']);
$this->extbaseConfigurationManagerService->configureForBackend();
} }
private function cleanupWorkarounds(): void private function cleanupWorkarounds(): void

View file

@ -75,7 +75,7 @@ final class CategoryRepository extends Repository
return $this->dataMapper->map( return $this->dataMapper->map(
Category::class, Category::class,
$qb->executeQuery()->fetchAll() $qb->executeQuery()->fetchAllAssociative()
); );
} }

View file

@ -304,7 +304,7 @@ final class DateRepository extends Repository
)->orderBy('tx_events_domain_model_date.start') )->orderBy('tx_events_domain_model_date.start')
; ;
return $statement->executeQuery()->fetchAll(); return $statement->executeQuery()->fetchAllAssociative();
} }
private function createEventConstraint( private function createEventConstraint(

View file

@ -18,7 +18,18 @@ namespace WerkraumMedia\Events\Domain\Repository;
*/ */
use TYPO3\CMS\Extbase\Persistence\Repository; use TYPO3\CMS\Extbase\Persistence\Repository;
use WerkraumMedia\Events\Domain\Model\Organizer;
final class OrganizerRepository extends Repository final class OrganizerRepository extends Repository
{ {
public function findOneByName(string $name): ?Organizer
{
$organizer = $this->findOneBy(['name' => $name]);
if ($organizer instanceof Organizer) {
return $organizer;
}
return null;
}
} }

View file

@ -72,7 +72,7 @@ final class AddSpecialProperties
$qb->where($qb->expr()->eq('postponed_date', $uidOfReferencedDate)); $qb->where($qb->expr()->eq('postponed_date', $uidOfReferencedDate));
$qb->setMaxResults(1); $qb->setMaxResults(1);
$result = $qb->executeQuery()->fetch(); $result = $qb->executeQuery()->fetchAssociative();
if ($result === false) { if ($result === false) {
return null; return null;

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace WerkraumMedia\Events\Service; namespace WerkraumMedia\Events\Service;
use RuntimeException;
use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
@ -13,9 +14,9 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
final class CategoryService final class CategoryService
{ {
private TimeTracker $timeTracker; private readonly TimeTracker $timeTracker;
private FrontendInterface $cache; private readonly FrontendInterface $cache;
public function __construct() public function __construct()
{ {
@ -77,17 +78,18 @@ final class CategoryService
->executeQuery() ->executeQuery()
; ;
while ($row = $res->fetch()) { foreach ($res->fetchAllAssociative() as $row) {
if (is_array($row) === false) {
continue;
}
$counter++; $counter++;
if ($counter > 10000) { if ($counter > 10000) {
$this->timeTracker->setTSlogMessage('EXT:dd_events: one or more recursive categories where found'); $this->timeTracker->setTSlogMessage('EXT:dd_events: one or more recursive categories where found');
return implode(',', $result); return implode(',', $result);
} }
$subcategories = $this->getChildrenCategoriesRecursive($row['uid'], $counter); $uid = $row['uid'];
if (is_numeric($uid) === false) {
throw new RuntimeException('Given uid was not numeric, which we never expect as UID column within DB is numeric.', 1728998121);
}
$subcategories = $this->getChildrenCategoriesRecursive((string)$uid, $counter);
$result[] = $row['uid'] . ($subcategories ? ',' . $subcategories : ''); $result[] = $row['uid'] . ($subcategories ? ',' . $subcategories : '');
} }
@ -100,11 +102,10 @@ final class CategoryService
*/ */
protected function getUidListFromRecords(string $idList): string protected function getUidListFromRecords(string $idList): string
{ {
$list = [];
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category') ->getQueryBuilderForTable('sys_category')
; ;
$rows = $queryBuilder $uids = $queryBuilder
->select('uid') ->select('uid')
->from('sys_category') ->from('sys_category')
->where($queryBuilder->expr()->in( ->where($queryBuilder->expr()->in(
@ -112,16 +113,9 @@ final class CategoryService
$queryBuilder->createNamedParameter(explode(',', $idList), Connection::PARAM_INT_ARRAY) $queryBuilder->createNamedParameter(explode(',', $idList), Connection::PARAM_INT_ARRAY)
)) ))
->executeQuery() ->executeQuery()
->fetchAll() ->fetchFirstColumn()
; ;
foreach ($rows as $row) {
if (is_array($row) === false) {
continue;
}
$list[] = $row['uid']; return implode(',', $uids);
}
return implode(',', $list);
} }
} }

View file

@ -24,7 +24,6 @@ namespace WerkraumMedia\Events\Service\Cleanup;
*/ */
use DateTimeImmutable; use DateTimeImmutable;
use PDO;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
@ -88,7 +87,7 @@ final class Database
$recordUids = $queryBuilder->select('event.uid') $recordUids = $queryBuilder->select('event.uid')
->from(self::EVENT_TABLE, 'event') ->from(self::EVENT_TABLE, 'event')
->leftJoin('event', self::DATE_TABLE, 'date', $queryBuilder->expr()->eq('date.event', 'event.uid'))->where($queryBuilder->expr()->isNull('date.uid'))->executeQuery() ->leftJoin('event', self::DATE_TABLE, 'date', $queryBuilder->expr()->eq('date.event', 'event.uid'))->where($queryBuilder->expr()->isNull('date.uid'))->executeQuery()
->fetchAll(PDO::FETCH_COLUMN) ->fetchFirstColumn()
; ;
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::EVENT_TABLE); $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::EVENT_TABLE);

View file

@ -23,7 +23,7 @@ namespace WerkraumMedia\Events\Service\Cleanup;
* 02110-1301, USA. * 02110-1301, USA.
*/ */
use PDO; use RuntimeException;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\StorageRepository; use TYPO3\CMS\Core\Resource\StorageRepository;
@ -84,16 +84,12 @@ final class Files
$referencesQuery->orderBy('tablenames'); $referencesQuery->orderBy('tablenames');
$referencesQuery->addOrderBy('uid_foreign'); $referencesQuery->addOrderBy('uid_foreign');
$references = $referencesQuery->executeQuery(); $references = $referencesQuery->executeQuery()->fetchAllAssociative();
$uidsPerTable = []; $uidsPerTable = [];
$referenceUidsToMarkAsDeleted = []; $referenceUidsToMarkAsDeleted = [];
while ($reference = $references->fetch()) { foreach ($references as $reference) {
if (is_array($reference) === false) {
continue;
}
if ($reference['tablenames'] === '') { if ($reference['tablenames'] === '') {
$referenceUidsToMarkAsDeleted[] = $reference['uid']; $referenceUidsToMarkAsDeleted[] = $reference['uid'];
continue; continue;
@ -112,7 +108,7 @@ final class Files
...$referenceUidsToMarkAsDeleted, ...$referenceUidsToMarkAsDeleted,
...array_keys(array_diff( ...array_keys(array_diff(
$records, $records,
$queryBuilder->executeQuery()->fetchAll(PDO::FETCH_COLUMN) $queryBuilder->executeQuery()->fetchFirstColumn()
)), )),
]; ];
} }
@ -249,11 +245,16 @@ final class Files
foreach ($queryBuilder->executeQuery()->iterateAssociative() as $reference) { foreach ($queryBuilder->executeQuery()->iterateAssociative() as $reference) {
$file = []; $file = [];
$fileUid = (int)$reference['uid_local']; $fileUid = (int)$reference['uid_local'];
$tableNames = $reference['tablenames'];
if (is_string($tableNames) === false) {
throw new RuntimeException('Fetched "tablenames" was not of type string. But it should be a string within the db.', 1728998600);
}
if ( if (
( (
str_starts_with((string)$reference['tablenames'], 'tx_events_domain_model_') str_starts_with($tableNames, 'tx_events_domain_model_')
|| $reference['tablenames'] === '' || $tableNames === ''
) && $reference['deleted'] == 1 ) && $reference['deleted'] == 1
) { ) {
$file = $files[$fileUid] ?? []; $file = $files[$fileUid] ?? [];

View file

@ -113,7 +113,14 @@ final class DataProcessingForModels implements SingletonInterface
private function getData(AbstractEntity $entity): array private function getData(AbstractEntity $entity): array
{ {
$row = $this->connection->select(['*'], $this->getTable($entity), ['uid' => $entity->getUid()])->fetch(); $row = $this->connection
->select(
['*'],
$this->getTable($entity),
['uid' => $entity->getUid()]
)
->fetchAssociative()
;
if (is_array($row)) { if (is_array($row)) {
return $row; return $row;
} }

View file

@ -38,14 +38,8 @@ final class DestinationDataImportService
private Event $tmpCurrentEvent; private Event $tmpCurrentEvent;
/** private readonly Logger $logger;
* @var Logger
*/
private $logger;
/**
* ImportService constructor.
*/
public function __construct( public function __construct(
private readonly EventRepository $eventRepository, private readonly EventRepository $eventRepository,
private readonly OrganizerRepository $organizerRepository, private readonly OrganizerRepository $organizerRepository,
@ -60,8 +54,10 @@ final class DestinationDataImportService
private readonly Slugger $slugger, private readonly Slugger $slugger,
private readonly CacheManager $cacheManager, private readonly CacheManager $cacheManager,
private readonly DataHandler $dataHandler, private readonly DataHandler $dataHandler,
private readonly EventDispatcher $eventDispatcher private readonly EventDispatcher $eventDispatcher,
LogManager $logManager,
) { ) {
$this->logger = $logManager->getLogger(self::class);
} }
public function import( public function import(
@ -83,7 +79,6 @@ final class DestinationDataImportService
// Set Configuration // Set Configuration
$this->configurationManager->setConfiguration(array_merge($frameworkConfiguration, $persistenceConfiguration)); $this->configurationManager->setConfiguration(array_merge($frameworkConfiguration, $persistenceConfiguration));
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(self::class);
$this->logger->info('Starting Destination Data Import Service'); $this->logger->info('Starting Destination Data Import Service');
try { try {
@ -412,7 +407,7 @@ final class DestinationDataImportService
private function getOrCreateEvent(string $globalId, string $title): Event private function getOrCreateEvent(string $globalId, string $title): Event
{ {
$event = $this->eventRepository->findOneByGlobalId($globalId); $event = $this->eventRepository->findOneBy(['globalId' => $globalId]);
if ($event instanceof Event) { if ($event instanceof Event) {
$this->logger->info( $this->logger->info(

View file

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\Events\Service\DestinationDataImportService;
final class ArrayBasedConfigurationService implements ConfigurationServiceInterface
{
public function __construct(
private readonly array $settings
) {
}
public function getLicenseKey(): string
{
return $this->settings['license'] ?? '';
}
public function getRestType(): string
{
return $this->settings['restType'] ?? '';
}
public function getRestMode(): string
{
return $this->settings['restMode'] ?? '';
}
public function getRestLimit(): string
{
return $this->settings['restLimit'] ?? '';
}
public function getRestTemplate(): string
{
return $this->settings['restTemplate'] ?? '';
}
public function getRestUrl(): string
{
return $this->settings['restUrl'] ?? '';
}
}

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\Events\Service\DestinationDataImportService;
interface ConfigurationServiceInterface
{
public function getLicenseKey(): string;
public function getRestType(): string;
public function getRestMode(): string;
public function getRestLimit(): string;
public function getRestTemplate(): string;
public function getRestUrl(): string;
}

View file

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\Events\Service\DestinationDataImportService;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use WerkraumMedia\Events\Service\ExtbaseConfigurationManagerService;
final class ExtbaseConfigurationService implements ConfigurationServiceInterface
{
private array $settings = [];
public function __construct(
private ExtbaseConfigurationManagerService $configurationManager
) {
}
public function getLicenseKey(): string
{
return $this->getSettings()['license'] ?? '';
}
public function getRestType(): string
{
return $this->getSettings()['restType'] ?? '';
}
public function getRestMode(): string
{
return $this->getSettings()['restMode'] ?? '';
}
public function getRestLimit(): string
{
return $this->getSettings()['restLimit'] ?? '';
}
public function getRestTemplate(): string
{
return $this->getSettings()['restTemplate'] ?? '';
}
public function getRestUrl(): string
{
return $this->getSettings()['restUrl'] ?? '';
}
private function getSettings(): array
{
if ($this->settings !== []) {
return $this->settings;
}
$fullTypoScript = $this->configurationManager
->getInstanceWithBackendContext()
->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT)
;
$this->settings = $fullTypoScript['module.']['tx_events.']['settings.']['destinationData.']
?? $fullTypoScript['module.']['tx_events_pi1.']['settings.']['destinationData.']
?? [];
return $this->settings;
}
}

View file

@ -27,7 +27,8 @@ use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use SplFileInfo; use SplFileInfo;
use TYPO3\CMS\Core\Log\LogManager; use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Resource\DuplicationBehavior; use TYPO3\CMS\Core\Resource\DuplicationBehavior as OldDuplicationBehavior;
use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\Index\MetaDataRepository; use TYPO3\CMS\Core\Resource\Index\MetaDataRepository;
@ -76,7 +77,14 @@ final class FilesAssignment
$this->logger->info('File already exists.', [$orgFileNameSanitized]); $this->logger->info('File already exists.', [$orgFileNameSanitized]);
} elseif ($filename = $this->loadFile($fileUrl)) { } elseif ($filename = $this->loadFile($fileUrl)) {
$this->logger->info('Adding file to FAL.', [$filename]); $this->logger->info('Adding file to FAL.', [$filename]);
$importFolder->addFile($filename, $orgFileNameSanitized, DuplicationBehavior::REPLACE);
// TODO: typo3/cms-core:14.0 Remove the fallback to old behaviour, only use new one.
$behaviour = OldDuplicationBehavior::REPLACE;
if (class_exists(DuplicationBehavior::class)) {
$behaviour = DuplicationBehavior::REPLACE;
}
$importFolder->addFile($filename, $orgFileNameSanitized, $behaviour);
} else { } else {
continue; continue;
} }

View file

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace WerkraumMedia\Events\Service\DestinationDataImportService; namespace WerkraumMedia\Events\Service\DestinationDataImportService;
use Generator; use Generator;
use PDO;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\DataHandling\SlugHelper; use TYPO3\CMS\Core\DataHandling\SlugHelper;
@ -61,18 +60,14 @@ final class Slugger
->from($tableName) ->from($tableName)
->where( ->where(
$queryBuilder->expr()->or( $queryBuilder->expr()->or(
$queryBuilder->expr()->eq($slugColumn, $queryBuilder->createNamedParameter('', PDO::PARAM_STR)), $queryBuilder->expr()->eq($slugColumn, $queryBuilder->createNamedParameter('')),
$queryBuilder->expr()->isNull($slugColumn) $queryBuilder->expr()->isNull($slugColumn)
) )
) )
->executeQuery() ->executeQuery()
; ;
while ($record = $statement->fetch()) { foreach ($statement->iterateAssociative() as $record) {
if (is_array($record) === false) {
continue;
}
yield $record; yield $record;
} }
} }
@ -88,7 +83,7 @@ final class Slugger
->where( ->where(
$queryBuilder->expr()->eq( $queryBuilder->expr()->eq(
'uid', 'uid',
$queryBuilder->createNamedParameter($record['uid'], PDO::PARAM_INT) $queryBuilder->createNamedParameter((int)$record['uid'])
) )
) )
->set($sluggerType->getSlugColumn(), $slug) ->set($sluggerType->getSlugColumn(), $slug)

View file

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace WerkraumMedia\Events\Service\DestinationDataImportService; namespace WerkraumMedia\Events\Service\DestinationDataImportService;
use TYPO3\CMS\Core\Http\Uri; use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager;
use WerkraumMedia\Events\Domain\Model\Import; use WerkraumMedia\Events\Domain\Model\Import;
/** /**
@ -13,18 +12,9 @@ use WerkraumMedia\Events\Domain\Model\Import;
*/ */
final class UrlFactory final class UrlFactory
{ {
/**
* @var array
*/
private $settings = [];
public function __construct( public function __construct(
BackendConfigurationManager $configurationManager private readonly ConfigurationServiceInterface $configuration,
) { ) {
$this->settings = $configurationManager->getConfiguration(
'Events',
'Pi1'
)['settings']['destinationData'] ?? [];
} }
/** /**
@ -35,17 +25,17 @@ final class UrlFactory
): string { ): string {
$parameter = [ $parameter = [
'experience' => $import->getRestExperience(), 'experience' => $import->getRestExperience(),
'licensekey' => $this->settings['license'] ?? '', 'licensekey' => $this->configuration->getLicenseKey(),
'type' => $this->settings['restType'] ?? '', 'type' => $this->configuration->getRestType(),
'mode' => $this->settings['restMode'] ?? '', 'mode' => $this->configuration->getRestMode(),
'limit' => $this->settings['restLimit'] ?? '', 'limit' => $this->configuration->getRestLimit(),
'template' => $this->settings['restTemplate'] ?? '', 'template' => $this->configuration->getRestTemplate(),
'q' => $import->getSearchQuery(), 'q' => $import->getSearchQuery(),
]; ];
$parameter = array_filter($parameter); $parameter = array_filter($parameter);
$url = new Uri($this->settings['restUrl']); $url = new Uri($this->configuration->getRestUrl());
$url = $url->withQuery(http_build_query($parameter)); $url = $url->withQuery(http_build_query($parameter));
return (string)$url; return (string)$url;
} }

View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace WerkraumMedia\Events\Service;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
final class ExtbaseConfigurationManagerService
{
public function __construct(
private readonly ConfigurationManagerInterface $configurationManager
) {
}
/**
* The mapper uses queries, which rely an on the configuration manager.
* But import is without request, so we ensure it is properly initialized.
*
* This should vanish, see: Documentation/Maintenance.rst
*/
public function configureForBackend(): void
{
// TODO: typo3/cms-core:14.0 Remove condition as this method is provided since 13.
if (method_exists($this->configurationManager, 'setRequest') === false) {
return;
}
$request = new ServerRequest();
$request = $request->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
$this->configurationManager->setRequest($request);
}
public function getInstanceWithBackendContext(): ConfigurationManagerInterface
{
$this->configureForBackend();
return $this->configurationManager;
}
}

View file

@ -234,9 +234,8 @@ class MigrateOldLocations implements UpgradeWizardInterface
{ {
$schema = $this->connectionPool $schema = $this->connectionPool
->getConnectionForTable('tx_events_domain_model_event') ->getConnectionForTable('tx_events_domain_model_event')
->getSchemaManager() ->getSchemaInformation()
->createSchema() ->introspectTable('tx_events_domain_model_event')
->getTable('tx_events_domain_model_event')
; ;
foreach ($this->columnsToFetch() as $column) { foreach ($this->columnsToFetch() as $column) {

View file

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* Copyright (C) 2024 Daniel Siepmann <daniel.siepmann@codappix.com>
*
* 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 WerkraumMedia\Events\Updates;
use TYPO3\CMS\Install\Attribute\UpgradeWizard;
use TYPO3\CMS\Install\Updates\AbstractListTypeToCTypeUpdate;
// TODO: typo3/cms-core:14.0 Remove condition as this class is provided since 13.
if (class_exists(AbstractListTypeToCTypeUpdate::class) === false) {
final class MigratePluginsFromListToCtype
{
}
return;
}
#[UpgradeWizard(MigratePluginsFromListToCtype::class)]
final class MigratePluginsFromListToCtype extends AbstractListTypeToCTypeUpdate
{
protected function getListTypeToCTypeMapping(): array
{
return [
'events_datelist' => 'events_datelist',
'events_datesearch' => 'events_datesearch',
'events_dateshow ' => 'events_dateshow',
'events_selected ' => 'events_selected',
];
}
public function getTitle(): string
{
return 'Migrate EXT:events content elements.';
}
public function getDescription(): string
{
return 'Migrate CType from list to dedicated plugins.';
}
}

View file

@ -2,127 +2,108 @@
<sheets> <sheets>
<sDEF> <sDEF>
<ROOT> <ROOT>
<TCEforms> <sheetTitle>Options</sheetTitle>
<sheetTitle>Options</sheetTitle>
</TCEforms>
<type>array</type> <type>array</type>
<el> <el>
<settings.sortByDate> <settings.sortByDate>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Sort By</label>
<label>Sort By</label> <config>
<config> <type>select</type>
<type>select</type> <renderType>selectSingle</renderType>
<renderType>selectSingle</renderType> <items type="array">
<items type="array"> <numIndex index="0" type="array">
<numIndex index="0" type="array"> <numIndex index="label">Start</numIndex>
<numIndex index="0">Start</numIndex> <numIndex index="value">start</numIndex>
<numIndex index="1">start</numIndex> </numIndex>
</numIndex> <numIndex index="1" type="array">
<numIndex index="1" type="array"> <numIndex index="label">End</numIndex>
<numIndex index="0">End</numIndex> <numIndex index="value">end</numIndex>
<numIndex index="1">end</numIndex> </numIndex>
</numIndex> </items>
</items> </config>
</config>
</TCEforms>
</settings.sortByDate> </settings.sortByDate>
<settings.sortOrder> <settings.sortOrder>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Sort Order</label>
<label>Sort Order</label> <config>
<config> <type>select</type>
<type>select</type> <renderType>selectSingle</renderType>
<renderType>selectSingle</renderType> <items type="array">
<items type="array"> <numIndex index="0" type="array">
<numIndex index="0" type="array"> <numIndex index="label">
<numIndex index="0"> Ascending
Ascending
</numIndex>
<numIndex index="1">ASC</numIndex>
</numIndex> </numIndex>
<numIndex index="1" type="array"> <numIndex index="value">ASC</numIndex>
<numIndex index="0"> </numIndex>
Descending <numIndex index="1" type="array">
</numIndex> <numIndex index="label">
<numIndex index="1">DESC</numIndex> Descending
</numIndex> </numIndex>
</items> <numIndex index="value">DESC</numIndex>
<default>ASC</default> </numIndex>
</config> </items>
</TCEforms> <default>ASC</default>
</config>
</settings.sortOrder> </settings.sortOrder>
<settings.limit> <settings.limit>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Max Items</label>
<label>Max Items</label> <config>
<config> <type>input</type>
<type>input</type> <size>10</size>
<size>10</size> <max>30</max>
<max>30</max> <eval>trim</eval>
<eval>trim</eval> </config>
</config>
</TCEforms>
</settings.limit> </settings.limit>
<settings.highlight> <settings.highlight>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Highlights only</label>
<label>Highlights only</label> <config>
<config> <type>check</type>
<type>check</type> <default>0</default>
<default>0</default> </config>
</config>
</TCEforms>
</settings.highlight> </settings.highlight>
<settings.todayOnly> <settings.todayOnly>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Today only</label>
<label>Today only</label> <config>
<config> <type>check</type>
<type>check</type> <default>0</default>
<default>0</default> </config>
</config>
</TCEforms>
</settings.todayOnly> </settings.todayOnly>
<settings.pagination> <settings.pagination>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Show pagination</label>
<label>Show pagination</label> <config>
<config> <type>check</type>
<type>check</type> <default>0</default>
<default>0</default> </config>
</config>
</TCEforms>
</settings.pagination> </settings.pagination>
<settings.showPID> <settings.showPID>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Detail page</label>
<label>Detail page</label> <config>
<config> <type>group</type>
<type>group</type> <allowed>pages</allowed>
<internal_type>db</internal_type> <size>1</size>
<allowed>pages</allowed> <maxitems>1</maxitems>
<size>1</size> <minitems>0</minitems>
<maxitems>1</maxitems> <show_thumbs>1</show_thumbs>
<minitems>0</minitems> </config>
<show_thumbs>1</show_thumbs>
</config>
</TCEforms>
</settings.showPID> </settings.showPID>
</el> </el>
</ROOT> </ROOT>
</sDEF> </sDEF>
<sTemplate> <sTemplate>
<ROOT> <ROOT>
<TCEforms> <sheetTitle>Template</sheetTitle>
<sheetTitle>Template</sheetTitle>
</TCEforms>
<type>array</type> <type>array</type>
<el> <el>
<settings.template> <settings.template>
@ -133,20 +114,20 @@
<renderType>selectSingle</renderType> <renderType>selectSingle</renderType>
<items type="array"> <items type="array">
<numIndex index="0" type="array"> <numIndex index="0" type="array">
<numIndex index="0">Default</numIndex> <numIndex index="label">Default</numIndex>
<numIndex index="1">default</numIndex> <numIndex index="value">default</numIndex>
</numIndex> </numIndex>
<numIndex index="1" type="array"> <numIndex index="1" type="array">
<numIndex index="0">Costum</numIndex> <numIndex index="label">Custom</numIndex>
<numIndex index="1">costum</numIndex> <numIndex index="value">costum</numIndex>
</numIndex> </numIndex>
<numIndex index="2" type="array"> <numIndex index="2" type="array">
<numIndex index="0">Table</numIndex> <numIndex index="label">Table</numIndex>
<numIndex index="1">table</numIndex> <numIndex index="value">table</numIndex>
</numIndex> </numIndex>
<numIndex index="3" type="array"> <numIndex index="3" type="array">
<numIndex index="0">Grid</numIndex> <numIndex index="label">Grid</numIndex>
<numIndex index="1">grid</numIndex> <numIndex index="value">grid</numIndex>
</numIndex> </numIndex>
</items> </items>
<default>default</default> <default>default</default>
@ -157,34 +138,29 @@
</sTemplate> </sTemplate>
<sConstrains> <sConstrains>
<ROOT> <ROOT>
<TCEforms> <sheetTitle>Regions &amp; Categories</sheetTitle>
<sheetTitle>Regions &amp; Categories</sheetTitle>
</TCEforms>
<type>array</type> <type>array</type>
<el> <el>
<settings.region> <settings.region>
<TCEforms> <label>Region</label>
<label>Region</label> <config>
<config> <type>select</type>
<type>select</type> <renderType>selectSingle</renderType>
<renderType>selectSingle</renderType> <items type="array">
<items type="array"> <numIndex index="0" type="array">
<numIndex index="0" type="array"> <numIndex index="label">Alle</numIndex>
<numIndex index="0">Alle</numIndex> <numIndex index="value"></numIndex>
<numIndex index="1"></numIndex> </numIndex>
</numIndex> </items>
</items> <foreign_table>tx_events_domain_model_region</foreign_table>
<foreign_table>tx_events_domain_model_region</foreign_table> <foreign_table_where>AND tx_events_domain_model_region.deleted = 0 AND tx_events_domain_model_region.hidden = 0</foreign_table_where>
<foreign_table_where>AND tx_events_domain_model_region.deleted = 0 AND tx_events_domain_model_region.hidden = 0</foreign_table_where> <size>1</size>
<size>1</size> <minitems>0</minitems>
<minitems>0</minitems> <maxitems>1</maxitems>
<maxitems>1</maxitems> </config>
</config>
</TCEforms>
</settings.region> </settings.region>
<settings.categoryCombination> <settings.categoryCombination>
<TCEforms>
<exclude>1</exclude> <exclude>1</exclude>
<label>Combination</label> <label>Combination</label>
<config> <config>
@ -192,54 +168,49 @@
<renderType>selectSingle</renderType> <renderType>selectSingle</renderType>
<items type="array"> <items type="array">
<numIndex index="0" type="array"> <numIndex index="0" type="array">
<numIndex index="0">And</numIndex> <numIndex index="label">And</numIndex>
<numIndex index="1">0</numIndex> <numIndex index="value">0</numIndex>
</numIndex> </numIndex>
<numIndex index="1" type="array"> <numIndex index="1" type="array">
<numIndex index="0">Or</numIndex> <numIndex index="label">Or</numIndex>
<numIndex index="1">1</numIndex> <numIndex index="value">1</numIndex>
</numIndex> </numIndex>
</items> </items>
<default>0</default> <default>0</default>
</config> </config>
</TCEforms>
</settings.categoryCombination> </settings.categoryCombination>
<settings.categories> <settings.categories>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>
<label> Category
Category </label>
</label> <config>
<config> <type>select</type>
<type>select</type> <renderType>selectTree</renderType>
<renderType>selectTree</renderType> <autoSizeMax>20</autoSizeMax>
<autoSizeMax>20</autoSizeMax> <foreign_table>sys_category</foreign_table>
<foreign_table>sys_category</foreign_table> <foreign_table_where> AND sys_category.sys_language_uid IN (-1, 0) ORDER BY sys_category.title ASC</foreign_table_where>
<foreign_table_where> AND sys_category.sys_language_uid IN (-1, 0) ORDER BY sys_category.title ASC</foreign_table_where> <maxitems>1</maxitems>
<maxitems>1</maxitems> <renderMode>tree</renderMode>
<renderMode>tree</renderMode> <size>8</size>
<size>8</size> <treeConfig>
<treeConfig> <appearance>
<appearance> <expandAll>1</expandAll>
<expandAll>1</expandAll> <showHeader>1</showHeader>
<showHeader>1</showHeader> </appearance>
</appearance> <parentField>parent</parentField>
<parentField>parent</parentField> </treeConfig>
</treeConfig> </config>
</config>
</TCEforms>
</settings.categories> </settings.categories>
<settings.includeSubcategories> <settings.includeSubcategories>
<TCEforms>
<exclude>1</exclude> <exclude>1</exclude>
<label>Include Subcategories</label> <label>Include Subcategories</label>
<config> <config>
<type>check</type> <type>check</type>
<default>0</default> <default>0</default>
</config> </config>
</TCEforms>
</settings.includeSubcategories> </settings.includeSubcategories>
</el> </el>
</ROOT> </ROOT>

View file

@ -2,35 +2,28 @@
<sheets> <sheets>
<sSearch> <sSearch>
<ROOT> <ROOT>
<TCEforms> <sheetTitle>Options</sheetTitle>
<sheetTitle>Options</sheetTitle>
</TCEforms>
<type>array</type> <type>array</type>
<el> <el>
<settings.pageUid> <settings.pageUid>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Results page</label>
<label>Results page</label> <config>
<config> <type>group</type>
<type>group</type> <allowed>pages</allowed>
<internal_type>db</internal_type> <size>1</size>
<allowed>pages</allowed> <maxitems>1</maxitems>
<size>1</size> <minitems>0</minitems>
<maxitems>1</maxitems> <show_thumbs>1</show_thumbs>
<minitems>0</minitems> </config>
<show_thumbs>1</show_thumbs>
</config>
</TCEforms>
</settings.pageUid> </settings.pageUid>
<settings.showRegions> <settings.showRegions>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>Show Regions</label>
<label>Show Regions</label> <config>
<config> <type>check</type>
<type>check</type> <default>0</default>
<default>0</default> </config>
</config>
</TCEforms>
</settings.showRegions> </settings.showRegions>
</el> </el>
</ROOT> </ROOT>

View file

@ -2,34 +2,27 @@
<sheets> <sheets>
<sDEF> <sDEF>
<ROOT> <ROOT>
<TCEforms> <sheetTitle>Options</sheetTitle>
<sheetTitle>Options</sheetTitle>
</TCEforms>
<type>array</type> <type>array</type>
<el> <el>
<settings.backPID> <settings.backPID>
<TCEforms>
<exclude>1</exclude> <exclude>1</exclude>
<label>Back page</label> <label>Back page</label>
<config> <config>
<type>group</type> <type>group</type>
<internal_type>db</internal_type>
<allowed>pages</allowed> <allowed>pages</allowed>
<size>1</size> <size>1</size>
<maxitems>1</maxitems> <maxitems>1</maxitems>
<minitems>0</minitems> <minitems>0</minitems>
<show_thumbs>1</show_thumbs> <show_thumbs>1</show_thumbs>
</config> </config>
</TCEforms>
</settings.backPID> </settings.backPID>
</el> </el>
</ROOT> </ROOT>
</sDEF> </sDEF>
<sTemplate> <sTemplate>
<ROOT> <ROOT>
<TCEforms> <sheetTitle>Template</sheetTitle>
<sheetTitle>Template</sheetTitle>
</TCEforms>
<type>array</type> <type>array</type>
<el> <el>
<settings.template> <settings.template>
@ -39,12 +32,12 @@
<type>select</type> <type>select</type>
<items type="array"> <items type="array">
<numIndex index="0" type="array"> <numIndex index="0" type="array">
<numIndex index="0">Default</numIndex> <numIndex index="label">Default</numIndex>
<numIndex index="1">default</numIndex> <numIndex index="value">default</numIndex>
</numIndex> </numIndex>
<numIndex index="1" type="array"> <numIndex index="1" type="array">
<numIndex index="0">Costum</numIndex> <numIndex index="label">Custom</numIndex>
<numIndex index="1">costum</numIndex> <numIndex index="value">costum</numIndex>
</numIndex> </numIndex>
</items> </items>
<default>default</default> <default>default</default>

View file

@ -5,17 +5,14 @@
<type>array</type> <type>array</type>
<el> <el>
<settings.selectedRecords> <settings.selectedRecords>
<TCEforms> <exclude>1</exclude>
<exclude>1</exclude> <label>LLL:EXT:events/Resources/Private/Language/de.locallang_db.xlf:tx_events.flexform.selected.selectedRecords</label>
<label>LLL:EXT:events/Resources/Private/Language/de.locallang_db.xlf:tx_events.flexform.selected.selectedRecords</label> <config>
<config> <type>group</type>
<type>group</type> <allowed>tx_events_domain_model_event</allowed>
<internal_type>db</internal_type> <minitems>1</minitems>
<allowed>tx_events_domain_model_event</allowed> <show_thumbs>1</show_thumbs>
<minitems>1</minitems> </config>
<show_thumbs>1</show_thumbs>
</config>
</TCEforms>
</settings.selectedRecords> </settings.selectedRecords>
</el> </el>
</ROOT> </ROOT>

16
Configuration/Icons.php Normal file
View file

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
return [
'events-plugin' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:events/Resources/Public/Icons/Extension.svg',
],
'pages-module-events' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:events/Resources/Public/Icons/Folder.svg',
],
];

View file

@ -43,7 +43,12 @@ services:
WerkraumMedia\Events\Updates\MigrateOldLocations: WerkraumMedia\Events\Updates\MigrateOldLocations:
public: true public: true
WerkraumMedia\Events\Service\DestinationDataImportService\ConfigurationServiceInterface:
alias: 'WerkraumMedia\Events\Service\DestinationDataImportService\ExtbaseConfigurationService'
WerkraumMedia\Events\Caching\PageCacheTimeout: WerkraumMedia\Events\Caching\PageCacheTimeout:
arguments:
'$runtimeCache': '@cache.runtime'
tags: tags:
- name: event.listener - name: event.listener
event: WerkraumMedia\Events\Events\Controller\DateListVariables event: WerkraumMedia\Events\Events\Controller\DateListVariables

View file

@ -7,54 +7,58 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
(function (string $extKey, string $table) { (function (string $extKey, string $table) {
/* Search Plugin */ /* Search Plugin */
ExtensionUtility::registerPlugin( $pluginSignature = ExtensionUtility::registerPlugin(
'Events', 'Events',
'DateSearch', 'DateSearch',
'Events: Date Search', 'Events: Date Search',
'EXT:events/Resources/Public/Icons/Extension.svg' 'EXT:events/Resources/Public/Icons/Extension.svg'
); );
$GLOBALS['TCA'][$table]['types']['list']['subtypes_addlist']['events_datesearch'] = 'pi_flexform'; ExtensionManagementUtility::addToAllTCAtypes($table, 'pi_flexform', $pluginSignature, 'after:subheader');
ExtensionManagementUtility::addPiFlexFormValue( ExtensionManagementUtility::addPiFlexFormValue(
'events_datesearch', '*',
'FILE:EXT:events/Configuration/FlexForms/DateSearch.xml' 'FILE:EXT:events/Configuration/FlexForms/DateSearch.xml',
$pluginSignature,
); );
/* Date List Plugin */ /* Date List Plugin */
ExtensionUtility::registerPlugin( $pluginSignature = ExtensionUtility::registerPlugin(
'Events', 'Events',
'DateList', 'DateList',
'Events: Date List', 'Events: Date List',
'EXT:events/Resources/Public/Icons/Extension.svg' 'EXT:events/Resources/Public/Icons/Extension.svg'
); );
$GLOBALS['TCA'][$table]['types']['list']['subtypes_addlist']['events_datelist'] = 'pi_flexform'; ExtensionManagementUtility::addToAllTCAtypes($table, 'pi_flexform', $pluginSignature, 'after:subheader');
ExtensionManagementUtility::addPiFlexFormValue( ExtensionManagementUtility::addPiFlexFormValue(
'events_datelist', '*',
'FILE:EXT:events/Configuration/FlexForms/DateList.xml' 'FILE:EXT:events/Configuration/FlexForms/DateList.xml',
$pluginSignature,
); );
/* Date Show Plugin */ /* Date Show Plugin */
ExtensionUtility::registerPlugin( $pluginSignature = ExtensionUtility::registerPlugin(
'Events', 'Events',
'DateShow', 'DateShow',
'Events: Date Show', 'Events: Date Show',
'EXT:events/Resources/Public/Icons/Extension.svg' 'EXT:events/Resources/Public/Icons/Extension.svg'
); );
$GLOBALS['TCA'][$table]['types']['list']['subtypes_addlist']['events_dateshow'] = 'pi_flexform'; ExtensionManagementUtility::addToAllTCAtypes($table, 'pi_flexform', $pluginSignature, 'after:subheader');
ExtensionManagementUtility::addPiFlexFormValue( ExtensionManagementUtility::addPiFlexFormValue(
'*',
'FILE:EXT:events/Configuration/FlexForms/DateShow.xml',
'events_dateshow', 'events_dateshow',
'FILE:EXT:events/Configuration/FlexForms/DateShow.xml'
); );
/* Event Selected Plugin */ /* Event Selected Plugin */
ExtensionUtility::registerPlugin( $pluginSignature = ExtensionUtility::registerPlugin(
'Events', 'Events',
'Selected', 'Selected',
'Events: Show selected', 'Events: Show selected',
'EXT:events/Resources/Public/Icons/Extension.svg' 'EXT:events/Resources/Public/Icons/Extension.svg'
); );
$GLOBALS['TCA'][$table]['types']['list']['subtypes_addlist']['events_selected'] = 'pi_flexform'; ExtensionManagementUtility::addToAllTCAtypes($table, 'pi_flexform', $pluginSignature, 'after:subheader');
ExtensionManagementUtility::addPiFlexFormValue( ExtensionManagementUtility::addPiFlexFormValue(
$pluginSignature,
'FILE:EXT:events/Configuration/FlexForms/Selected.xml',
'events_selected', 'events_selected',
'FILE:EXT:events/Configuration/FlexForms/Selected.xml'
); );
})('events', 'tt_content'); })('events', 'tt_content');

View file

@ -346,7 +346,6 @@ return [
'config' => [ 'config' => [
'type' => 'category', 'type' => 'category',
'minitems' => 0, 'minitems' => 0,
'multiple' => true,
], ],
], ],
'features' => [ 'features' => [

View file

@ -0,0 +1,35 @@
5.0.0
=====
Breaking
--------
* Only when being on 13.x or higher:
Content elements are no longer registered as `list` but with their own CType.
An upgrade wizard is provided that can be executed to migrate existing database
entries.
But custom TypoScript and modifications need to be adopted.
We recommend not to use the provided plugins but build your own tailored content
elements instead.
Features
--------
* Add Support for TYPO3 v13.4 LTS.
Fixes
-----
Nothing
Tasks
-----
Nothing
Deprecation
-----------
Nothing

View file

@ -43,6 +43,8 @@ Table of Contents
Settings Settings
Changelog Changelog
Maintenance
.. toctree:: .. toctree::
:hidden: :hidden:

View file

@ -0,0 +1,16 @@
Maintenance
===========
Migrate to SiteSets
-------------------
We currently leverage TypoScript for some configuration during import.
This is a bad idea, TypoScript is not intended for this usage.
We should migrate to Site Sets in the future.
The import should use the page uid for import records to resolve the site and fetch
corresponding settings.
That way the import can provide all the necessary information and can be passed down
to all classes.

View file

@ -1,11 +0,0 @@
[general]
project = Events Extension
copyright = since 2019 by Dirk Koritnik and Daniel Siepmann
[html_theme_options]
github_branch = main
github_repository = werkraum-media/events
project_home = https://github.com/werkraum-media/events/
project_issues = https://github.com/werkraum-media/events/issues
project_repository = https://github.com/werkraum-media/events/

21
Documentation/guides.xml Normal file
View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<guides
xmlns="https://www.phpdoc.org/guides"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.phpdoc.org/guides ../vendor/phpdocumentor/guides-cli/resources/schema/guides.xsd"
links-are-relative="true"
>
<extension
class="\T3Docs\Typo3DocsTheme\DependencyInjection\Typo3DocsThemeExtension"
project-home="https://github.com/werkraum-media/events/"
project-repository="https://github.com/werkraum-media/events/"
project-issues="https://github.com/werkraum-media/events/issues"
edit-on-github-branch="main"
edit-on-github="werkraum-media/events"
typo3-core-preferred="stable"
/>
<project
title="Events Extension"
copyright="since 2019 by Dirk Koritnik and Daniel Siepmann"
/>
</guides>

View file

@ -149,6 +149,7 @@ abstract class AbstractFunctionalTestCase extends FunctionalTestCase
array $argumentsAndOptions = ['configurationUid' => '1'], array $argumentsAndOptions = ['configurationUid' => '1'],
string $command = ImportDestinationDataViaConfigruationCommand::class string $command = ImportDestinationDataViaConfigruationCommand::class
): CommandTester { ): CommandTester {
GeneralUtility::setContainer($this->getcontainer());
$subject = $this->get($command); $subject = $this->get($command);
self::assertInstanceOf(Command::class, $subject); self::assertInstanceOf(Command::class, $subject);

View file

@ -68,8 +68,7 @@ class CacheTest extends AbstractFunctionalTestCase
(new PhpDataSet())->import(['tt_content' => [[ (new PhpDataSet())->import(['tt_content' => [[
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_datelisttest',
'list_type' => 'events_datelisttest',
'header' => 'All Dates', 'header' => 'All Dates',
]]]); ]]]);
$this->setUpFrontendRendering(); $this->setUpFrontendRendering();
@ -306,7 +305,7 @@ class CacheTest extends AbstractFunctionalTestCase
// We might be seconds off due to our created offset within the rendering. // We might be seconds off due to our created offset within the rendering.
$value = (int)$value; $value = (int)$value;
$age = ((int)$end->format('U')) - time(); $age = ((int)$end->format('U')) - time();
self::assertLessThanOrEqual($age + 3, $value, 'Max age of cached response is higher than expected.'); self::assertLessThanOrEqual($age + 4, $value, 'Max age of cached response is higher than expected.');
self::assertGreaterThanOrEqual($age - 3, $value, 'Max age of cached response is less than expected.'); self::assertGreaterThanOrEqual($age - 3, $value, 'Max age of cached response is less than expected.');
} }

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_dateshow',
'list_type' => 'events_dateshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_dateshow',
'list_type' => 'events_dateshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_dateshow',
'list_type' => 'events_dateshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_dateshow',
'list_type' => 'events_dateshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_dateshow',
'list_type' => 'events_dateshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_datelist',
'list_type' => 'events_datelist',
'header' => 'All Dates', 'header' => 'All Dates',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_datelist',
'list_type' => 'events_datelist',
'header' => 'All Dates', 'header' => 'All Dates',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
[ [
'uid' => 1, 'uid' => 1,
'pid' => 1, 'pid' => 1,
'CType' => 'list', 'CType' => 'events_datelist',
'list_type' => 'events_datelist',
'header' => 'Upcoming Dates', 'header' => 'Upcoming Dates',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_eventshow',
'list_type' => 'events_eventshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_eventshow',
'list_type' => 'events_eventshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_eventshow',
'list_type' => 'events_eventshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
0 => [ 0 => [
'uid' => '1', 'uid' => '1',
'pid' => '1', 'pid' => '1',
'CType' => 'list', 'CType' => 'events_eventshow',
'list_type' => 'events_eventshow',
'header' => 'Singleview', 'header' => 'Singleview',
], ],
], ],

View file

@ -7,8 +7,7 @@ return [
[ [
'pid' => '1', 'pid' => '1',
'uid' => '1', 'uid' => '1',
'CType' => 'list', 'CType' => 'events_datelist',
'list_type' => 'events_datelist',
'header' => 'Kino Events', 'header' => 'Kino Events',
'pi_flexform' => '<?xml version="1.0" encoding="utf-8" standalone="yes" ?> 'pi_flexform' => '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<T3FlexForms> <T3FlexForms>

View file

@ -7,8 +7,7 @@ return [
[ [
'pid' => '1', 'pid' => '1',
'uid' => '1', 'uid' => '1',
'CType' => 'list', 'CType' => 'events_datelist',
'list_type' => 'events_datelist',
'header' => 'Kino Events', 'header' => 'Kino Events',
'pi_flexform' => '<?xml version="1.0" encoding="utf-8" standalone="yes" ?> 'pi_flexform' => '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<T3FlexForms> <T3FlexForms>

View file

@ -7,8 +7,7 @@ return [
[ [
'pid' => '1', 'pid' => '1',
'uid' => '1', 'uid' => '1',
'CType' => 'list', 'CType' => 'events_datelist',
'list_type' => 'events_datelist',
'header' => 'Kino Events', 'header' => 'Kino Events',
'pi_flexform' => '<?xml version="1.0" encoding="utf-8" standalone="yes" ?> 'pi_flexform' => '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<T3FlexForms> <T3FlexForms>

View file

@ -23,6 +23,10 @@ declare(strict_types=1);
namespace WerkraumMedia\EventsExample; namespace WerkraumMedia\EventsExample;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
@ -35,9 +39,24 @@ final class UserFunc
$this->cObj = $cObj; $this->cObj = $cObj;
} }
public function accessTsfeTimeout(): string public function accessTsfeTimeout(string $content, array $configuration, ServerRequestInterface $request): string
{ {
return 'get_cache_timeout: ' . $this->getTsfe()->get_cache_timeout(); // TODO: typo3/cms-core:14.0 Remove this code block as this won't work since v13.x anymore
if (is_callable([$this->getTsfe(), 'get_cache_timeout'])) {
return 'get_cache_timeout: ' . $this->getTsfe()->get_cache_timeout();
}
$pageInformation = $request->getAttribute('frontend.page.information');
$typoScriptConfigArray = $request->getAttribute('frontend.typoscript')->getConfigArray();
return 'get_cache_timeout: ' . GeneralUtility::makeInstance(CacheLifetimeCalculator::class)
->calculateLifetimeForPage(
$pageInformation->getId(),
$pageInformation->getPageRecord(),
$typoScriptConfigArray,
0,
GeneralUtility::makeInstance(Context::class)
)
;
} }
public function sleep(string $content, array $configuration): string public function sleep(string $content, array $configuration): string

View file

@ -9,11 +9,15 @@ use WerkraumMedia\Events\Controller\EventController;
ExtensionUtility::configurePlugin( ExtensionUtility::configurePlugin(
'Events', 'Events',
'DateListTest', 'DateListTest',
[DateController::class => 'list'] [DateController::class => 'list'],
[],
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT,
); );
ExtensionUtility::configurePlugin( ExtensionUtility::configurePlugin(
'Events', 'Events',
'EventShow', 'EventShow',
[EventController::class => 'show'] [EventController::class => 'show'],
[],
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT,
); );

View file

@ -11,6 +11,7 @@ return [
'storage_pid' => '2', 'storage_pid' => '2',
'features_pid' => '3', 'features_pid' => '3',
'features_parent' => '4', 'features_parent' => '4',
'files_folder' => '1:/staedte/beispielstadt/events/',
], ],
], ],
'pages' => [ 'pages' => [

View file

@ -9,6 +9,7 @@ return [
'pid' => '2', 'pid' => '2',
'title' => 'Example for test', 'title' => 'Example for test',
'storage_pid' => '2', 'storage_pid' => '2',
'files_folder' => '1:/staedte/beispielstadt/events/',
], ],
], ],
'pages' => [ 'pages' => [

View file

@ -10,6 +10,7 @@ return [
'title' => 'Example import configuration', 'title' => 'Example import configuration',
'storage_pid' => '2', 'storage_pid' => '2',
'rest_experience' => 'beispielstadt', 'rest_experience' => 'beispielstadt',
'files_folder' => '1:/staedte/beispielstadt/events/',
], ],
], ],
]; ];

View file

@ -8,8 +8,8 @@ use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\MockObject\Stub;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager;
use WerkraumMedia\Events\Domain\Model\Import; use WerkraumMedia\Events\Domain\Model\Import;
use WerkraumMedia\Events\Service\DestinationDataImportService\ArrayBasedConfigurationService;
use WerkraumMedia\Events\Service\DestinationDataImportService\UrlFactory; use WerkraumMedia\Events\Service\DestinationDataImportService\UrlFactory;
class UrlFactoryTest extends TestCase class UrlFactoryTest extends TestCase
@ -17,8 +17,7 @@ class UrlFactoryTest extends TestCase
#[Test] #[Test]
public function canBeCreated(): void public function canBeCreated(): void
{ {
$configurationManager = $this->createStub(BackendConfigurationManager::class); $configurationManager = new ArrayBasedConfigurationService([]);
$configurationManager->method('getConfiguration')->willReturn([]);
$subject = new UrlFactory( $subject = new UrlFactory(
$configurationManager $configurationManager
@ -37,8 +36,7 @@ class UrlFactoryTest extends TestCase
array $settings, array $settings,
string $expectedResult string $expectedResult
): void { ): void {
$configurationManager = $this->createStub(BackendConfigurationManager::class); $configurationManager = new ArrayBasedConfigurationService($settings);
$configurationManager->method('getConfiguration')->willReturn(['settings' => ['destinationData' => $settings]]);
$subject = new UrlFactory( $subject = new UrlFactory(
$configurationManager $configurationManager

View file

@ -21,14 +21,14 @@
"require": { "require": {
"php": "~8.1.0 || ~8.2.0 || ~8.3.0", "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"symfony/console": "^6.4 || ^7.0", "symfony/console": "^6.4 || ^7.0",
"symfony/dependency-injection": "^6.4 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0 || ^7.1",
"typo3/cms-core": "^12.4", "typo3/cms-core": "^12.4 || ^13.4",
"typo3/cms-extbase": "^12.4", "typo3/cms-extbase": "^12.4 || ^13.4",
"typo3/cms-filelist": "^12.4", "typo3/cms-filelist": "^12.4 || ^13.4",
"typo3/cms-filemetadata": "^12.4", "typo3/cms-filemetadata": "^12.4 || ^13.4",
"typo3/cms-fluid": "^12.4", "typo3/cms-fluid": "^12.4 || ^13.4",
"typo3/cms-frontend": "^12.4", "typo3/cms-frontend": "^12.4 || ^13.4",
"typo3/cms-install": "^12.4" "typo3/cms-install": "^12.4 || ^13.4"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -69,10 +69,11 @@
"phpstan/extension-installer": "^1.1", "phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.10", "phpstan/phpstan": "^1.10",
"phpstan/phpstan-phpunit": "1.3.15", "phpstan/phpstan-phpunit": "1.3.15",
"saschaegerer/phpstan-typo3": "1.9.0", "saschaegerer/phpstan-typo3": "^1.10",
"typo3/cms-backend": "^12.4", "staabm/phpstan-todo-by": "^0.1.28",
"typo3/cms-fluid-styled-content": "^12.4", "typo3/cms-backend": "^12.4 || ^13.4",
"typo3/cms-seo": "^12.4", "typo3/cms-fluid-styled-content": "^12.4 || ^13.4",
"typo3/cms-seo": "^12.4 || ^13.4",
"typo3/testing-framework": "^8.0" "typo3/testing-framework": "^8.0"
}, },
"config": { "config": {

View file

@ -9,7 +9,7 @@ $EM_CONF['events'] = [
'author' => 'Dirk Koritnik, Daniel Siepmann', 'author' => 'Dirk Koritnik, Daniel Siepmann',
'author_email' => 'koritnik@werkraum-media.de, coding@daniel-siepmann.de', 'author_email' => 'koritnik@werkraum-media.de, coding@daniel-siepmann.de',
'state' => 'stable', 'state' => 'stable',
'version' => '4.2.1', 'version' => '5.0.0',
'constraints' => [ 'constraints' => [
'depends' => [ 'depends' => [
'typo3' => '', 'typo3' => '',

View file

@ -2,9 +2,6 @@
declare(strict_types=1); declare(strict_types=1);
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
use TYPO3\CMS\Core\Imaging\IconRegistry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\ExtensionUtility; use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
use WerkraumMedia\Events\Controller\DateController; use WerkraumMedia\Events\Controller\DateController;
use WerkraumMedia\Events\Controller\EventController; use WerkraumMedia\Events\Controller\EventController;
@ -16,27 +13,32 @@ call_user_func(function () {
'Events', 'Events',
'DateSearch', 'DateSearch',
[DateController::class => 'search'], [DateController::class => 'search'],
[DateController::class => 'search'] [DateController::class => 'search'],
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
); );
ExtensionUtility::configurePlugin( ExtensionUtility::configurePlugin(
'Events', 'Events',
'DateList', 'DateList',
[DateController::class => 'list'], [DateController::class => 'list'],
[DateController::class => 'list'] [DateController::class => 'list'],
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
); );
ExtensionUtility::configurePlugin( ExtensionUtility::configurePlugin(
'Events', 'Events',
'DateShow', 'DateShow',
[DateController::class => 'show'], [DateController::class => 'show'],
[DateController::class => 'show'] [DateController::class => 'show'],
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
); );
ExtensionUtility::configurePlugin( ExtensionUtility::configurePlugin(
'Events', 'Events',
'Selected', 'Selected',
[EventController::class => 'list'] [EventController::class => 'list'],
[],
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
); );
if ( if (
@ -47,16 +49,4 @@ call_user_func(function () {
} }
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = '^events_search'; $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = '^events_search';
$iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
$iconRegistry->registerIcon(
'events-plugin',
SvgIconProvider::class,
['source' => 'EXT:events/Resources/Public/Icons/Extension.svg']
);
$iconRegistry->registerIcon(
'pages-module-events',
SvgIconProvider::class,
['source' => 'EXT:events/Resources/Public/Icons/Folder.svg']
);
}); });

View file

@ -6,13 +6,8 @@ parameters:
path: Classes/Controller/DateController.php path: Classes/Controller/DateController.php
- -
message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, 'strval' given\\.$#" message: "#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(bool\\|float\\|int\\|resource\\|string\\|null\\)\\: string given\\.$#"
count: 1 count: 2
path: Classes/Domain/DestinationData/ImportFactory.php
-
message: "#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#"
count: 1
path: Classes/Domain/DestinationData/ImportFactory.php path: Classes/Domain/DestinationData/ImportFactory.php
- -