diff --git a/Classes/Importer/JsonImporter.php b/Classes/Importer/JsonImporter.php index 553edf44..9d02ead1 100644 --- a/Classes/Importer/JsonImporter.php +++ b/Classes/Importer/JsonImporter.php @@ -12,13 +12,20 @@ namespace JWeiland\Events2\Importer; use JWeiland\Events2\Configuration\ImportConfiguration; +use JWeiland\Events2\Helper\PathSegmentHelper; +use JWeiland\Events2\Service\CategoryService; +use JWeiland\Events2\Service\LocationService; +use JWeiland\Events2\Service\OrganizerService; use Psr\Log\LoggerInterface; +use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction; use TYPO3\CMS\Core\DataHandling\DataHandler; +use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException; +use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Reactions\Authentication\ReactionUserAuthentication; @@ -31,6 +38,11 @@ class JsonImporter { public function __construct( protected readonly LoggerInterface $logger, + protected readonly PathSegmentHelper $pathSegmentHelper, + protected readonly CategoryService $categoryService, + protected readonly LocationService $locationService, + protected readonly OrganizerService $organizerService, + protected readonly ResourceFactory $resourceFactory, ) { } @@ -43,7 +55,7 @@ public function import(ImportConfiguration $importConfiguration): bool foreach ($eventsImportDataChunk as $eventImportData) { $this->updateDataMapForEventImport( $eventImportData, - $importConfiguration->getStoragePid(), + $importConfiguration, $dataMap ); } @@ -59,6 +71,9 @@ public function import(ImportConfiguration $importConfiguration): bool } return false; } + + // Can only be done after storing the records, as we need the UID + $this->updateSlugs($dataHandler); } catch (\Exception $e) { $this->logger->error($e->getMessage()); return false; @@ -70,8 +85,11 @@ public function import(ImportConfiguration $importConfiguration): bool return true; } - protected function updateDataMapForEventImport(array $eventImportData, int $storagePid, array &$dataMap): void - { + protected function updateDataMapForEventImport( + array $eventImportData, + ImportConfiguration $configuration, + array &$dataMap + ): void { $eventRecord = $this->getEventRecordByImportId($eventImportData['uid']); // Early return, if record was already imported. @@ -84,7 +102,7 @@ protected function updateDataMapForEventImport(array $eventImportData, int $stor $eventUid = $this->getUniqueIdForNewRecords(); $dataMap['tx_events2_domain_model_event'][$eventUid] = [ - 'pid' => $storagePid, + 'pid' => $configuration->getStoragePid(), 'crdate' => time(), 'tstamp' => time(), 'starttime' => $this->migrateDateToTimestamp($eventImportData['starttime']), @@ -93,29 +111,28 @@ protected function updateDataMapForEventImport(array $eventImportData, int $stor 'event_type' => $eventImportData['event_type'], 'top_of_list' => $eventImportData['top_of_list'] ? 1 : 0, 'title' => $eventImportData['title'], - 'path_segment' => $eventImportData['path_segment'], 'event_begin' => $this->migrateDateToTimestamp($eventImportData['event_begin']), - 'event_time' => $this->migrateTimeRecordToDataMap($eventImportData['event_time'], $storagePid, $dataMap), + 'event_time' => $this->migrateTimeRecordToDataMap($eventImportData['event_time'], $configuration->getStoragePid(), $dataMap), 'event_end' => $this->migrateDateToTimestamp($eventImportData['event_end']), 'same_day' => $eventImportData['same_day'] ? 1 : 0, - 'multiple_times' => $this->migrateTimeRecordsToDataMap($eventImportData['multiple_times'], $storagePid, $dataMap), + 'multiple_times' => $this->migrateTimeRecordsToDataMap($eventImportData['multiple_times'], $configuration->getStoragePid(), $dataMap), 'xth' => $eventImportData['xth'], 'weekday' => $eventImportData['weekday'], - 'different_times' => $this->migrateTimeRecordsToDataMap($eventImportData['different_times'], $storagePid, $dataMap), + 'different_times' => $this->migrateTimeRecordsToDataMap($eventImportData['different_times'], $configuration->getStoragePid(), $dataMap), 'each_weeks' => $eventImportData['each_weeks'], 'each_months' => $eventImportData['each_months'], 'recurring_end' => $this->migrateDateToTimestamp($eventImportData['recurring_end']), - 'exceptions' => $this->migrateExceptionsToDataMap($eventImportData['exceptions'], $storagePid, $dataMap), + 'exceptions' => $this->migrateExceptionsToDataMap($eventImportData['exceptions'], $configuration->getStoragePid(), $dataMap), 'teaser' => $eventImportData['teaser'], 'detail_information' => $eventImportData['detail_information'], 'free_entry' => $eventImportData['free_entry'] ? 1 : 0, - 'ticket_link' => $this->migrateLinkRecordToDataMap($eventImportData['ticket_link'], $storagePid, $dataMap), - // 'categories' => $eventImportData['categories'], - 'location' => $this->migrateLocationRecordToDataMap($eventImportData['location'], $storagePid, $dataMap), - 'organizers' => $this->migrateOrganizerRecordsToDataMap($eventImportData['organizers'], $storagePid, $dataMap), - // 'images' => $eventImportData['images'], - 'video_link' => $this->migrateLinkRecordToDataMap($eventImportData['video_link'], $storagePid, $dataMap), - 'download_links' => $this->migrateLinkRecordsToDataMap($eventImportData['download_links'], $storagePid, $dataMap), + 'ticket_link' => $this->migrateLinkRecordToDataMap($eventImportData['ticket_link'], $configuration->getStoragePid(), $dataMap), + 'categories' => $this->migrateCategoryRecordsToDataMap($eventImportData['categories'], $configuration->getStoragePid(), $dataMap), + 'location' => $this->migrateLocationRecordToDataMap($eventImportData['location'], $configuration->getStoragePid(), $dataMap), + 'organizers' => $this->migrateOrganizerRecordsToDataMap($eventImportData['organizers'], $configuration->getStoragePid(), $dataMap), + 'images' => $this->migrateImageRecordsToDataMap($eventImportData['images'], $eventUid, $configuration, $dataMap), + 'video_link' => $this->migrateLinkRecordToDataMap($eventImportData['video_link'], $configuration->getStoragePid(), $dataMap), + 'download_links' => $this->migrateLinkRecordsToDataMap($eventImportData['download_links'], $configuration->getStoragePid(), $dataMap), ]; } @@ -134,23 +151,23 @@ protected function migrateDateToTimestamp(?string $date): int return $date->getTimestamp(); } - protected function migrateTimeRecordsToDataMap(array $timeRecords, $storagePid, array &$dataMap): string + protected function migrateTimeRecordsToDataMap(array $importTimeRecords, int $storagePid, array &$dataMap): string { - if ($timeRecords === []) { + if ($importTimeRecords === []) { return '0'; } $timeUidCollection = []; - foreach ($timeRecords as $timeRecord) { + foreach ($importTimeRecords as $timeRecord) { $timeUidCollection[] = $this->migrateTimeRecordToDataMap($timeRecord, $storagePid, $dataMap); } return implode(',', $timeUidCollection); } - protected function migrateTimeRecordToDataMap(array $timeRecord, int $storagePid, array &$dataMap): string + protected function migrateTimeRecordToDataMap(array $importTimeRecord, int $storagePid, array &$dataMap): string { - if ($timeRecord === []) { + if ($importTimeRecord === []) { return '0'; } @@ -160,36 +177,36 @@ protected function migrateTimeRecordToDataMap(array $timeRecord, int $storagePid 'pid' => $storagePid, 'crdate' => time(), 'tstamp' => time(), - 'starttime' => $this->migrateDateToTimestamp($timeRecord['starttime']), - 'endtime' => $this->migrateDateToTimestamp($timeRecord['endtime']), - 'hidden' => $timeRecord['hidden'] ? 1 : 0, - 'weekday' => $timeRecord['weekday'], - 'time_begin' => $timeRecord['time_begin'], - 'time_entry' => $timeRecord['time_entry'], - 'duration' => $timeRecord['duration'], - 'time_end' => $timeRecord['time_end'], + 'starttime' => $this->migrateDateToTimestamp($importTimeRecord['starttime']), + 'endtime' => $this->migrateDateToTimestamp($importTimeRecord['endtime']), + 'hidden' => $importTimeRecord['hidden'] ? 1 : 0, + 'weekday' => $importTimeRecord['weekday'], + 'time_begin' => $importTimeRecord['time_begin'], + 'time_entry' => $importTimeRecord['time_entry'], + 'duration' => $importTimeRecord['duration'], + 'time_end' => $importTimeRecord['time_end'], ]; return $timeUid; } - protected function migrateLinkRecordsToDataMap(array $linkRecords, int $storagePid, array &$dataMap): string + protected function migrateLinkRecordsToDataMap(array $importLinkRecords, int $storagePid, array &$dataMap): string { - if ($linkRecords === []) { + if ($importLinkRecords === []) { return '0'; } $linkUidCollection = []; - foreach ($linkRecords as $linkRecord) { + foreach ($importLinkRecords as $linkRecord) { $linkUidCollection[] = $this->migrateLinkRecordToDataMap($linkRecord, $storagePid, $dataMap); } return implode(',', $linkUidCollection); } - protected function migrateLinkRecordToDataMap(array $linkRecord, int $storagePid, array &$dataMap): string + protected function migrateLinkRecordToDataMap(array $importLinkRecord, int $storagePid, array &$dataMap): string { - if ($linkRecord === []) { + if ($importLinkRecord === []) { return ''; } @@ -199,39 +216,39 @@ protected function migrateLinkRecordToDataMap(array $linkRecord, int $storagePid 'pid' => $storagePid, 'crdate' => time(), 'tstamp' => time(), - 'starttime' => $this->migrateDateToTimestamp($linkRecord['starttime']), - 'endtime' => $this->migrateDateToTimestamp($linkRecord['endtime']), - 'hidden' => $linkRecord['hidden'] ? 1 : 0, - 'title' => $linkRecord['title'], - 'link' => $linkRecord['link'], + 'starttime' => $this->migrateDateToTimestamp($importLinkRecord['starttime']), + 'endtime' => $this->migrateDateToTimestamp($importLinkRecord['endtime']), + 'hidden' => $importLinkRecord['hidden'] ? 1 : 0, + 'title' => $importLinkRecord['title'], + 'link' => $importLinkRecord['link'], ]; return $linkUid; } - protected function migrateExceptionsToDataMap(array $exceptions, $storagePid, array &$dataMap): string + protected function migrateExceptionsToDataMap(array $importExceptionRecords, int $storagePid, array &$dataMap): string { - if ($exceptions === []) { + if ($importExceptionRecords === []) { return '0'; } $exceptionUidCollection = []; - foreach ($exceptions as $exception) { + foreach ($importExceptionRecords as $importExceptionRecord) { $exceptionUid = $this->getUniqueIdForNewRecords(); $dataMap['tx_events2_domain_model_exception'][$exceptionUid] = [ 'pid' => $storagePid, 'crdate' => time(), 'tstamp' => time(), - 'starttime' => $this->migrateDateToTimestamp($exception['starttime']), - 'endtime' => $this->migrateDateToTimestamp($exception['endtime']), - 'hidden' => $exception['hidden'] ? 1 : 0, - 'exception_type' => $exception['exception_type'], - 'exception_date' => $this->migrateDateToTimestamp($exception['exception_date']), - 'exception_time' => $this->migrateTimeRecordToDataMap($exception['exception_time'], $storagePid, $dataMap), - 'exception_details' => $exception['exception_details'], - 'show_anyway' => $exception['show_anyway'] ? 1 : 0, - 'mark_as' =>$exception['mark_as'], + 'starttime' => $this->migrateDateToTimestamp($importExceptionRecord['starttime']), + 'endtime' => $this->migrateDateToTimestamp($importExceptionRecord['endtime']), + 'hidden' => $importExceptionRecord['hidden'] ? 1 : 0, + 'exception_type' => $importExceptionRecord['exception_type'], + 'exception_date' => $this->migrateDateToTimestamp($importExceptionRecord['exception_date']), + 'exception_time' => $this->migrateTimeRecordToDataMap($importExceptionRecord['exception_time'], $storagePid, $dataMap), + 'exception_details' => $importExceptionRecord['exception_details'], + 'show_anyway' => $importExceptionRecord['show_anyway'] ? 1 : 0, + 'mark_as' =>$importExceptionRecord['mark_as'], ]; $exceptionUidCollection[] = $exceptionUid; @@ -240,53 +257,149 @@ protected function migrateExceptionsToDataMap(array $exceptions, $storagePid, ar return implode(',', $exceptionUidCollection); } - protected function migrateLocationRecordToDataMap(array $locationRecord, $storagePid, array &$dataMap): string + protected function migrateCategoryRecordsToDataMap(array $importCategoryRecords, int $storagePid, array &$dataMap): string { - if ($locationRecord === []) { - return ''; + if ($importCategoryRecords === []) { + return '0'; } - $locationUid = $this->getUniqueIdForNewRecords(); + $categoryUidCollection = []; + foreach ($importCategoryRecords as $importCategoryRecord) { + $categoryRecord = $this->categoryService->getCategoryRecordByTitle($importCategoryRecord['title'] ?? ''); + if ($categoryRecord === null) { + $categoryUid = $this->getUniqueIdForNewRecords(); + + $dataMap['tx_events2_domain_model_location'][$categoryUid] = [ + 'pid' => $storagePid, + 'crdate' => time(), + 'tstamp' => time(), + 'starttime' => $this->migrateDateToTimestamp($importCategoryRecord['starttime']), + 'endtime' => $this->migrateDateToTimestamp($importCategoryRecord['endtime']), + 'hidden' => $importCategoryRecord['hidden'] ? 1 : 0, + 'title' => $importCategoryRecord['title'], + 'parent' => 0, + ]; + } else { + $categoryUid = (string)$importCategoryRecord['uid']; + } - $dataMap['tx_events2_domain_model_location'][$locationUid] = [ - 'pid' => $storagePid, - 'crdate' => time(), - 'tstamp' => time(), - 'starttime' => $this->migrateDateToTimestamp($locationRecord['starttime']), - 'endtime' => $this->migrateDateToTimestamp($locationRecord['endtime']), - 'hidden' => $locationRecord['hidden'] ? 1 : 0, - 'location' => $locationRecord['location'], - ]; + $categoryUidCollection[] = $categoryUid; + } - return $locationUid; + return implode(',', $categoryUidCollection); } - protected function migrateOrganizerRecordsToDataMap(array $organizerRecords, $storagePid, array &$dataMap): string + protected function migrateLocationRecordToDataMap(array $importLocationRecord, int $storagePid, array &$dataMap): string { - if ($organizerRecords === []) { - return '0'; + if ($importLocationRecord === []) { + return ''; } - $organizerUidCollection = []; - foreach ($organizerRecords as $organizerRecord) { - $organizerUid = $this->getUniqueIdForNewRecords(); + $locationRecord = $this->locationService->getLocationRecordByTitle($importLocationRecord['location'] ?? ''); + + if ($locationRecord === null) { + $locationUid = $this->getUniqueIdForNewRecords(); - $dataMap['tx_events2_domain_model_organizer'][$organizerUid] = [ + $dataMap['tx_events2_domain_model_location'][$locationUid] = [ 'pid' => $storagePid, 'crdate' => time(), 'tstamp' => time(), - 'starttime' => $this->migrateDateToTimestamp($organizerRecord['starttime']), - 'endtime' => $this->migrateDateToTimestamp($organizerRecord['endtime']), - 'hidden' => $organizerRecord['hidden'] ? 1 : 0, - 'organizer' => $organizerRecord['organizer'], - 'link' => $this->migrateLinkRecordToDataMap($organizerRecord['link'], $storagePid, $dataMap), + 'starttime' => $this->migrateDateToTimestamp($importLocationRecord['starttime']), + 'endtime' => $this->migrateDateToTimestamp($importLocationRecord['endtime']), + 'hidden' => $importLocationRecord['hidden'] ? 1 : 0, + 'location' => $importLocationRecord['location'], ]; + } else { + $locationUid = $locationRecord['uid']; + } + + return (string)$locationUid; + } + + protected function migrateOrganizerRecordsToDataMap(array $importOrganizerRecords, int $storagePid, array &$dataMap): string + { + if ($importOrganizerRecords === []) { + return ''; + } + + $organizerUidCollection = []; + foreach ($importOrganizerRecords as $importOrganizerRecord) { + $organizerRecord = $this->organizerService->getOrganizerRecordByTitle($importOrganizerRecord['organizer'] ?? ''); + if ($organizerRecord === null) { + $organizerUid = $this->getUniqueIdForNewRecords(); + + $dataMap['tx_events2_domain_model_organizer'][$organizerUid] = [ + 'pid' => $storagePid, + 'crdate' => time(), + 'tstamp' => time(), + 'starttime' => $this->migrateDateToTimestamp($importOrganizerRecord['starttime']), + 'endtime' => $this->migrateDateToTimestamp($importOrganizerRecord['endtime']), + 'hidden' => $importOrganizerRecord['hidden'] ? 1 : 0, + 'organizer' => $importOrganizerRecord['organizer'], + 'link' => $this->migrateLinkRecordToDataMap($importOrganizerRecord['link'], $storagePid, $dataMap), + ]; + } else { + $organizerUid = (string)$organizerRecord['uid']; + } $organizerUidCollection[] = $organizerUid; } return implode(',', $organizerUidCollection); + } + + protected function migrateImageRecordsToDataMap( + array $importImageRecords, + string $eventUid, + ImportConfiguration $configuration, + array &$dataMap + ): string { + if ($importImageRecords === []) { + return '0'; + } + + $sysFileReferenceUidCollection = []; + foreach ($importImageRecords as $imageRecord) { + if (!GeneralUtility::isValidUrl($imageRecord['url'])) { + continue; + } + + $resourceStorage = $this->resourceFactory->getStorageObjectFromCombinedIdentifier( + $configuration->getStorageFolder() + ); + $folder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($configuration->getStorageFolder()); + $filename = $resourceStorage->sanitizeFileName(pathinfo($imageRecord['url'], PATHINFO_BASENAME)); + + if ($folder->hasFile($filename)) { + $fileObject = $folder->getFile($filename); + } else { + try { + $tempImagePath = GeneralUtility::tempnam('events2_import_'); + file_put_contents($tempImagePath, GeneralUtility::getUrl($imageRecord['url'])); + $fileObject = $resourceStorage->addFile( + $tempImagePath, + $folder, + $filename + ); + } catch (ExistingTargetFileNameException $e) { + continue; + } + } + $sysFileReferenceUid = $this->getUniqueIdForNewRecords(); + + $dataMap['sys_file_reference'][$sysFileReferenceUid] = [ + 'uid_local' => $fileObject->getUid(), + 'tablenames' => 'tx_events2_domain_model_event', + 'uid_foreign' => $eventUid, + 'fieldname' => 'images', + 'pid' => $configuration->getStoragePid(), + ]; + + $sysFileReferenceUidCollection[] = $sysFileReferenceUid; + } + + return implode(',', $sysFileReferenceUidCollection); } /** @@ -323,6 +436,37 @@ protected function getEventRecordByImportId(int $importId): ?array return is_array($eventRecord) ? $eventRecord : null; } + /** + * If you found another solution how to retrieve the just stored records from DataHandler: Give me a note + */ + protected function updateSlugs(DataHandler $dataHandler): void + { + // Collect all NEW[hash] values valid for event table + $eventNEWidValues = array_filter($dataHandler->substNEWwithIDs_table, static function (string $table): bool { + return $table === 'tx_events2_domain_model_event'; + }); + + // Get real event INSERT ID for NEW[hash] values + $eventUidValues = array_filter($dataHandler->substNEWwithIDs, static function (string $newId) use ($eventNEWidValues): bool { + return array_key_exists($newId, $eventNEWidValues); + }, ARRAY_FILTER_USE_KEY); + + $connection = $this->getConnectionPool()->getConnectionForTable('tx_events2_domain_model_event'); + foreach ($eventUidValues as $eventUid) { + $eventRecord = BackendUtility::getRecord('tx_events2_domain_model_event', (int)$eventUid); + $uniqueSlug = $this->pathSegmentHelper->generatePathSegment($eventRecord); + $connection->update( + 'tx_events2_domain_model_event', + [ + 'path_segment' => $uniqueSlug, + ], + [ + 'uid' => (int)$eventUid, + ] + ); + } + } + protected function getQueryBuilderForTable(string $table, bool $allowHidden = false): QueryBuilder { $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable($table); diff --git a/Classes/Service/CategoryService.php b/Classes/Service/CategoryService.php new file mode 100644 index 00000000..241fb172 --- /dev/null +++ b/Classes/Service/CategoryService.php @@ -0,0 +1,65 @@ +getQueryBuilder(); + try { + $categoryRecord = $queryBuilder->select('*') + ->from(self::TABLE) + ->where( + $queryBuilder->expr()->eq( + 'title', + $queryBuilder->createNamedParameter($title) + ), + $queryBuilder->expr()->eq( + 'sys_language_uid', + $queryBuilder->createNamedParameter($language, Connection::PARAM_INT) + ) + ) + ->executeQuery() + ->fetchAssociative(); + } catch (Exception $e) { + return null; + } + + return is_array($categoryRecord) ? $categoryRecord : null; + } + + protected function getQueryBuilder(): QueryBuilder + { + $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + + return $queryBuilder; + } + + protected function getConnectionPool(): ConnectionPool + { + return GeneralUtility::makeInstance(ConnectionPool::class); + } +} diff --git a/Classes/Service/LocationService.php b/Classes/Service/LocationService.php new file mode 100644 index 00000000..dc832cc0 --- /dev/null +++ b/Classes/Service/LocationService.php @@ -0,0 +1,65 @@ +getQueryBuilder(); + try { + $locationRecord = $queryBuilder->select('*') + ->from(self::TABLE) + ->where( + $queryBuilder->expr()->eq( + 'location', + $queryBuilder->createNamedParameter($title) + ), + $queryBuilder->expr()->eq( + 'sys_language_uid', + $queryBuilder->createNamedParameter($language, Connection::PARAM_INT) + ) + ) + ->executeQuery() + ->fetchAssociative(); + } catch (Exception $e) { + return null; + } + + return is_array($locationRecord) ? $locationRecord : null; + } + + protected function getQueryBuilder(): QueryBuilder + { + $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + + return $queryBuilder; + } + + protected function getConnectionPool(): ConnectionPool + { + return GeneralUtility::makeInstance(ConnectionPool::class); + } +} diff --git a/Classes/Service/OrganizerService.php b/Classes/Service/OrganizerService.php new file mode 100644 index 00000000..f76b6808 --- /dev/null +++ b/Classes/Service/OrganizerService.php @@ -0,0 +1,65 @@ +getQueryBuilder(); + try { + $organizerRecord = $queryBuilder->select('*') + ->from(self::TABLE) + ->where( + $queryBuilder->expr()->eq( + 'organizer', + $queryBuilder->createNamedParameter($title) + ), + $queryBuilder->expr()->eq( + 'sys_language_uid', + $queryBuilder->createNamedParameter($language, Connection::PARAM_INT) + ) + ) + ->executeQuery() + ->fetchAssociative(); + } catch (Exception $e) { + return null; + } + + return is_array($organizerRecord) ? $organizerRecord : null; + } + + protected function getQueryBuilder(): QueryBuilder + { + $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + + return $queryBuilder; + } + + protected function getConnectionPool(): ConnectionPool + { + return GeneralUtility::makeInstance(ConnectionPool::class); + } +}