Initial commit - Typo3 11.5.41

This commit is contained in:
Matteo Gallo
2026-07-03 17:53:31 +02:00
commit 5ca4743197
6811 changed files with 568848 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
<?php
namespace GeorgRinger\News\Domain\Service;
use GeorgRinger\News\Domain\Model\Dto\EmConfiguration;
use GeorgRinger\News\Domain\Repository\CategoryRepository;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Folder;
use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
use TYPO3\CMS\Core\Resource\ProcessedFile;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Resource\ResourceStorage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
class AbstractImportService implements LoggerAwareInterface
{
use LoggerAwareTrait;
const UPLOAD_PATH = 'uploads/tx_news/';
/**
* @var ObjectManager
*/
protected $objectManager;
/**
* @var PersistenceManager
*/
protected $persistenceManager;
/**
* @var array
*/
protected $postPersistQueue = [];
/**
* @var EmConfiguration
*/
protected $emSettings;
/**
* @var Folder
*/
protected $importFolder;
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* @var CategoryRepository
*/
protected $categoryRepository;
/**
* AbstractImportService constructor.
* @param PersistenceManager $persistenceManager
* @param EmConfiguration $emSettings
* @param ObjectManager $objectManager
* @param CategoryRepository $categoryRepository
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(
PersistenceManager $persistenceManager,
ObjectManager $objectManager,
CategoryRepository $categoryRepository,
EventDispatcherInterface $eventDispatcher
) {
$this->emSettings = GeneralUtility::makeInstance(EmConfiguration::class);
$this->persistenceManager = $persistenceManager;
$this->objectManager = $objectManager;
$this->categoryRepository = $categoryRepository;
$this->eventDispatcher = $eventDispatcher;
}
/**
* Compares 2 files by using its filesize
*
* @param string $file1 Absolute path and filename to file1
* @param string $file2 Absolute path and filename to file2
* @return bool
*/
protected function filesAreEqual($file1, $file2): bool
{
return filesize($file1) === filesize($file2);
}
/**
* Find a existing file by its hash
*
* @param string $hash
*
* @return File|ProcessedFile|null
*/
protected function findFileByHash($hash)
{
$file = null;
$files = $this->getFileIndexRepository()->findByContentHash($hash);
if (count($files)) {
foreach ($files as $fileInfo) {
if ($fileInfo['storage'] > 0) {
$file = $this->getResourceFactory()->getFileObjectByStorageAndIdentifier(
$fileInfo['storage'],
$fileInfo['identifier']
);
break;
}
}
}
return $file;
}
/**
* Get import Folder
*
* TODO: catch exception when storage/folder does not exist and return readable message to the user
*
* @return Folder
*/
protected function getImportFolder(): Folder
{
if ($this->importFolder === null) {
$this->importFolder = $this->getResourceFactory()->getFolderObjectFromCombinedIdentifier($this->emSettings->getStorageUidImporter() . ':' . $this->emSettings->getResourceFolderImporter());
}
return $this->importFolder;
}
/**
* Returns an instance of the FileIndexRepository
*
* @return FileIndexRepository
*/
protected function getFileIndexRepository(): FileIndexRepository
{
return FileIndexRepository::getInstance();
}
/**
* Get resource storage
*
* @return ResourceStorage
*/
protected function getResourceStorage(): ResourceStorage
{
return $this->getResourceFactory()->getStorageObject($this->emSettings->getStorageUidImporter());
}
/**
* @return ResourceFactory
*/
protected function getResourceFactory(): ResourceFactory
{
/** @var ResourceFactory $resourceFactory */
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
return $resourceFactory;
}
}

View File

@@ -0,0 +1,263 @@
<?php
namespace GeorgRinger\News\Domain\Service;
use GeorgRinger\News\Domain\Model\Category;
use GeorgRinger\News\Domain\Model\Dto\EmConfiguration;
use GeorgRinger\News\Domain\Model\FileReference;
use GeorgRinger\News\Domain\Repository\CategoryRepository;
use GeorgRinger\News\Event\CategoryImportPostHydrateEvent;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
class CategoryImportService extends AbstractImportService
{
const ACTION_SET_PARENT_CATEGORY = 1;
const ACTION_CREATE_L10N_CHILDREN_CATEGORY = 2;
/**
* CategoryImportService constructor.
* @param PersistenceManager $persistenceManager
* @param EmConfiguration $emSettings
* @param ObjectManager $objectManager
* @param CategoryRepository $categoryRepository
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(
PersistenceManager $persistenceManager,
ObjectManager $objectManager,
CategoryRepository $categoryRepository,
EventDispatcherInterface $eventDispatcher
) {
parent::__construct($persistenceManager, $objectManager, $categoryRepository, $eventDispatcher);
}
/**
* @param array $importArray
*
* @return void
*/
public function import(array $importArray): void
{
$this->logger->info(sprintf('Starting import for %s categories', count($importArray)));
// Sort import array to import the default language first
foreach ($importArray as $importItem) {
$category = $this->hydrateCategory($importItem);
if (!empty($importItem['title_lang_ol'])) {
$this->postPersistQueue[$importItem['import_id']] = [
'category' => $category,
'importItem' => $importItem,
'action' => self::ACTION_CREATE_L10N_CHILDREN_CATEGORY,
'titleLanguageOverlay' => $importItem['title_lang_ol']
];
}
if ($importItem['parentcategory']) {
$this->postPersistQueue[$importItem['import_id']] = [
'category' => $category,
'action' => self::ACTION_SET_PARENT_CATEGORY,
'parentCategoryOriginUid' => $importItem['parentcategory']
];
}
}
$this->persistenceManager->persistAll();
foreach ($this->postPersistQueue as $queueItem) {
switch ($queueItem['action']) {
case self::ACTION_SET_PARENT_CATEGORY:
$this->setParentCategory($queueItem);
break;
case self::ACTION_CREATE_L10N_CHILDREN_CATEGORY:
$this->createL10nChildrenCategory($queueItem);
break;
default:
// do nothing
break;
}
}
$this->persistenceManager->persistAll();
}
/**
* Hydrate a category record with the given array
*
* @param array $importItem
*
* @return array|object
*/
protected function hydrateCategory(array $importItem)
{
$category = $this->categoryRepository->findOneByImportSourceAndImportId(
$importItem['import_source'],
$importItem['import_id']
);
$this->logger->info(sprintf(
'Import of category from source "%s" with id "%s"',
$importItem['import_source'],
$importItem['import_id']
));
if (is_null($category)) {
$this->logger->info('Category is new');
$category = GeneralUtility::makeInstance(Category::class);
$this->categoryRepository->add($category);
} else {
$this->logger->info(sprintf('Category exists already with id "%s".', $category->getUid()));
}
$category->setPid($importItem['pid']);
$category->setHidden($importItem['hidden']);
$category->setStarttime($importItem['starttime']);
$category->setEndtime($importItem['endtime']);
$category->setCrdate($importItem['crdate']);
$category->setTstamp($importItem['tstamp']);
$category->setTitle($importItem['title']);
$category->setDescription($importItem['description']);
if (!empty($importItem['image'])) {
$this->setFileRelationFromImage($category, $importItem['image']);
}
$category->setShortcut($importItem['shortcut']);
$category->setSinglePid($importItem['single_pid']);
$category->setImportId($importItem['import_id']);
$category->setImportSource($importItem['import_source']);
$event = $this->eventDispatcher->dispatch(new CategoryImportPostHydrateEvent($this, $importItem, $category));
return $event->getCategory();
}
/**
* Add category image when not already present
*
* @param Category $category
* @param $image
*
* @return void
*/
protected function setFileRelationFromImage($category, $image)
{
// get fileObject by given identifier (file UID, combined identifier or path/filename)
try {
$newImage = $this->getResourceFactory()->retrieveFileOrFolderObject($image);
} catch (ResourceDoesNotExistException $exception) {
$newImage = false;
}
// only proceed if image is found
if (!$newImage instanceof File) {
return;
}
// new image found check if this isn't already
$existingImages = $category->getImages();
if (!is_null($existingImages) && $existingImages->count() !== 0) {
/** @var $item FileReference */
foreach ($existingImages as $item) {
// only check already persisted items
if ($item->getFileUid() === (int)$newImage->getUid()
||
($item->getUid() &&
$item->getOriginalResource()->getName() === $newImage->getName() &&
$item->getOriginalResource()->getSize() === (int)$newImage->getSize())
) {
$newImage = false;
break;
}
}
}
if ($newImage) {
// file not inside a storage then search for existing file or copy the one form storage 0 to the import folder
if ($newImage->getStorage()->getUid() === 0) {
// search DB for same file based on hash (to prevent duplicates)
$existingFile = $this->findFileByHash($newImage->getSha1());
// no exciting file then copy file to import folder
if ($existingFile === null) {
$newImage = $this->getResourceStorage()->copyFile($newImage, $this->getImportFolder());
} else {
$newImage = $existingFile;
}
}
$fileReference = GeneralUtility::makeInstance(FileReference::class);
$fileReference->setFileUid($newImage->getUid());
$fileReference->setPid($category->getPid());
$category->addImage($fileReference);
}
}
/**
* Set parent category
*
* @param array $queueItem
*
* @return void
*/
protected function setParentCategory(array $queueItem): void
{
/** @var $category Category */
$category = $queueItem['category'];
$parentCategoryOriginUid = $queueItem['parentCategoryOriginUid'];
if (is_null($parentCategory = $this->postPersistQueue[$parentCategoryOriginUid]['category'])) {
$parentCategory = $this->categoryRepository->findOneByImportSourceAndImportId(
$category->getImportSource(),
$parentCategoryOriginUid
);
}
if ($parentCategory !== null) {
$category->setParentcategory($parentCategory);
$this->categoryRepository->update($category);
}
}
/**
* Create l10n relation
*
* @param array $queueItem
*
* @return void
*/
protected function createL10nChildrenCategory(array $queueItem): void
{
/** @var $category Category */
$category = $queueItem['category'];
$titleLanguageOverlay = GeneralUtility::trimExplode('|', $queueItem['titleLanguageOverlay']);
foreach ($titleLanguageOverlay as $key => $title) {
$sysLanguageUid = $key + 1;
$importItem = $queueItem['importItem'];
$importItem['import_id'] = $importItem['import_id'] . '|L:' . $sysLanguageUid;
/** @var $l10nChildrenCategory Category */
$l10nChildrenCategory = $this->hydrateCategory($importItem);
$this->categoryRepository->add($l10nChildrenCategory);
$l10nChildrenCategory->setTitle($title);
$l10nChildrenCategory->setL10nParent((int)$category->getUid());
$l10nChildrenCategory->setSysLanguageUid((int)$sysLanguageUid);
}
}
}

View File

@@ -0,0 +1,411 @@
<?php
namespace GeorgRinger\News\Domain\Service;
use GeorgRinger\News\Domain\Model\Dto\EmConfiguration;
use GeorgRinger\News\Domain\Model\FileReference;
use GeorgRinger\News\Domain\Model\Link;
use GeorgRinger\News\Domain\Model\News;
use GeorgRinger\News\Domain\Repository\CategoryRepository;
use GeorgRinger\News\Domain\Repository\NewsRepository;
use GeorgRinger\News\Domain\Repository\TtContentRepository;
use GeorgRinger\News\Event\NewsImportPostHydrateEvent;
use GeorgRinger\News\Event\NewsImportPreHydrateEvent;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
class NewsImportService extends AbstractImportService
{
const ACTION_IMPORT_L10N_OVERLAY = 1;
/**
* @var NewsRepository
*/
protected $newsRepository;
/**
* @var TtContentRepository
*/
protected $ttContentRepository;
/**
* @var array
*/
protected $settings = [];
/**
* NewsImportService constructor.
* @param PersistenceManager $persistenceManager
* @param EmConfiguration $emSettings
* @param ObjectManager $objectManager
* @param CategoryRepository $categoryRepository
* @param EventDispatcherInterface $eventDispatcher
* @param NewsRepository $newsRepository
* @param TtContentRepository $ttContentRepository
*/
public function __construct(
PersistenceManager $persistenceManager,
ObjectManager $objectManager,
CategoryRepository $categoryRepository,
EventDispatcherInterface $eventDispatcher,
NewsRepository $newsRepository,
TtContentRepository $ttContentRepository
) {
parent::__construct($persistenceManager, $objectManager, $categoryRepository, $eventDispatcher);
$this->newsRepository = $newsRepository;
$this->ttContentRepository = $ttContentRepository;
}
/**
* @param array $importItem
*
* @return array|object
*/
protected function initializeNewsRecord(array $importItem)
{
$news = null;
$this->logger->info(sprintf(
'Import of news from source "%s" with id "%s"',
$importItem['import_source'],
$importItem['import_id']
));
if ($importItem['import_source'] && $importItem['import_id']) {
$news = $this->newsRepository->findOneByImportSourceAndImportId(
$importItem['import_source'],
$importItem['import_id']
);
}
if ($news === null) {
$news = GeneralUtility::makeInstance(News::class);
$this->newsRepository->add($news);
} else {
$this->logger->info(sprintf('News exists already with id "%s".', $news->getUid()));
$this->newsRepository->update($news);
}
return $news;
}
/**
* @param News $news
* @param array $importItem
* @param array $importItemOverwrite
* @return News
*/
protected function hydrateNewsRecord(
News $news,
array $importItem,
array $importItemOverwrite
): News {
if (!empty($importItemOverwrite)) {
$importItem = array_merge($importItem, $importItemOverwrite);
}
$news->setPid($importItem['pid']);
$news->setHidden($importItem['hidden']);
if ($importItem['starttime']) {
$news->setStarttime($importItem['starttime']);
}
if ($importItem['endtime']) {
$news->setStarttime($importItem['endtime']);
}
if (!empty($importItem['fe_group'])) {
$news->setFeGroup((string)$importItem['fe_group']);
}
$news->setTstamp($importItem['tstamp']);
$news->setCrdate($importItem['crdate']);
$news->setSysLanguageUid($importItem['sys_language_uid']);
$news->setSorting((int)$importItem['sorting']);
$news->setTitle($importItem['title']);
$news->setTeaser($importItem['teaser']);
$news->setBodytext($importItem['bodytext']);
$news->setType((string)$importItem['type']);
$news->setKeywords($importItem['keywords']);
$news->setDescription($importItem['description']);
$news->setDatetime(new \DateTime(date('Y-m-d H:i:sP', $importItem['datetime'])));
$news->setArchive(new \DateTime(date('Y-m-d H:i:sP', $importItem['archive'])));
$contentElementUidArray = GeneralUtility::trimExplode(',', $importItem['content_elements'], true);
foreach ($contentElementUidArray as $contentElementUid) {
if (is_object($contentElement = $this->ttContentRepository->findByUid($contentElementUid))) {
$news->addContentElement($contentElement);
}
}
$news->setInternalurl($importItem['internalurl']);
$news->setExternalurl($importItem['externalurl']);
$news->setAuthor($importItem['author']);
$news->setAuthorEmail($importItem['author_email']);
$news->setImportId($importItem['import_id']);
$news->setImportSource($importItem['import_source']);
$news->setPathSegment($importItem['path_segment']);
if (is_array($importItem['categories'])) {
foreach ($importItem['categories'] as $categoryUid) {
if ($this->settings['findCategoriesByImportSource']) {
$category = $this->categoryRepository->findOneByImportSourceAndImportId(
$this->settings['findCategoriesByImportSource'],
$categoryUid
);
} else {
$category = $this->categoryRepository->findByUid($categoryUid);
}
if ($category) {
$news->addCategory($category);
} else {
$this->logger->warning(sprintf('Category with ID "%s" was not found', $categoryUid));
}
}
}
// media relation
if (is_array($importItem['media'])) {
foreach ($importItem['media'] as $mediaItem) {
// get fileobject by given identifier (file UID, combined identifier or path/filename)
try {
$file = $this->getResourceFactory()->retrieveFileOrFolderObject($mediaItem['image']);
} catch (ResourceDoesNotExistException $exception) {
$file = null;
}
// no file found skip processing of this item
if ($file === null) {
continue;
}
// file not inside a storage then search for same file based on hash (to prevent duplicates)
if ($file->getStorage()->getUid() === 0) {
$existingFile = $this->findFileByHash($file->getSha1());
if ($existingFile !== null) {
$file = $existingFile;
}
}
/** @var $media FileReference */
if (!$media = $this->getIfFalRelationIfAlreadyExists($news->getFalMedia(), $file)) {
// file not inside a storage copy the one form storage 0 to the import folder
if ($file->getStorage()->getUid() === 0) {
$file = $this->getResourceStorage()->copyFile($file, $this->getImportFolder());
}
$media = GeneralUtility::makeInstance(FileReference::class);
$media->setFileUid($file->getUid());
$news->addFalMedia($media);
}
if ($media) {
$media->setTitle($mediaItem['title']);
$media->setAlternative($mediaItem['alt']);
$media->setDescription($mediaItem['caption']);
$media->setShowinpreview($mediaItem['showinpreview']);
$media->setPid($importItem['pid']);
}
}
}
// related files
if (is_array($importItem['related_files'])) {
foreach ($importItem['related_files'] as $fileItem) {
// get fileObject by given identifier (file UID, combined identifier or path/filename)
try {
$file = $this->getResourceFactory()->retrieveFileOrFolderObject($fileItem['file']);
} catch (ResourceDoesNotExistException $exception) {
$file = null;
}
// no file found skip processing of this item
if ($file === null) {
continue;
}
// file not inside a storage then search for same file based on hash (to prevent duplicates)
if ($file->getStorage()->getUid() === 0) {
$existingFile = $this->findFileByHash($file->getSha1());
if ($existingFile !== null) {
$file = $existingFile;
}
}
/** @var $relatedFile FileReference */
if (!$relatedFile = $this->getIfFalRelationIfAlreadyExists($news->getFalRelatedFiles(), $file)) {
// file not inside a storage copy the one form storage 0 to the import folder
if ($file->getStorage()->getUid() === 0) {
$file = $this->getResourceStorage()->copyFile($file, $this->getImportFolder());
}
$relatedFile = GeneralUtility::makeInstance(FileReference::class);
$relatedFile->setFileUid($file->getUid());
$news->addFalRelatedFile($relatedFile);
}
if ($relatedFile) {
$relatedFile->setTitle($fileItem['title']);
$relatedFile->setDescription($fileItem['description']);
$relatedFile->setPid($importItem['pid']);
}
}
}
if (is_array($importItem['related_links'])) {
foreach ($importItem['related_links'] as $link) {
/** @var $relatedLink Link */
if (($relatedLink = $this->getRelatedLinkIfAlreadyExists($news, $link['uri'])) === false) {
$relatedLink = GeneralUtility::makeInstance(Link::class);
$relatedLink->setUri($link['uri']);
$news->addRelatedLink($relatedLink);
}
$relatedLink->setTitle($link['title']);
$relatedLink->setDescription($link['description']);
$relatedLink->setPid($importItem['pid']);
}
}
$event = $this->eventDispatcher->dispatch(new NewsImportPostHydrateEvent($this, $importItem, $news));
return $event->getNews();
}
/**
* Import
*
* @param array $importData
* @param array $importItemOverwrite
* @param array $settings
*
* @return void
*/
public function import(array $importData, array $importItemOverwrite = [], $settings = []): void
{
$this->settings = $settings;
$this->logger->info(sprintf('Starting import for %s news', count($importData)));
foreach ($importData as $importItem) {
$event = $this->eventDispatcher->dispatch(new NewsImportPreHydrateEvent($this, $importItem));
$importItem = $event->getImportItem();
// Store language overlay in post persist queue
if ((int)$importItem['sys_language_uid'] > 0 && (string)$importItem['l10n_parent'] !== '0') {
$this->postPersistQueue[$importItem['import_id']] = [
'action' => self::ACTION_IMPORT_L10N_OVERLAY,
'category' => null,
'importItem' => $importItem
];
continue;
}
$news = $this->initializeNewsRecord($importItem);
$this->hydrateNewsRecord($news, $importItem, $importItemOverwrite);
}
$this->persistenceManager->persistAll();
foreach ($this->postPersistQueue as $queueItem) {
if ($queueItem['action'] == self::ACTION_IMPORT_L10N_OVERLAY) {
$this->importL10nOverlay($queueItem, $importItemOverwrite);
}
}
$this->persistenceManager->persistAll();
}
/**
* @param array $queueItem
* @param array $importItemOverwrite
*
* @return void
*/
protected function importL10nOverlay(array $queueItem, array $importItemOverwrite): void
{
$importItem = $queueItem['importItem'];
$parentNews = $this->newsRepository->findOneByImportSourceAndImportId(
$importItem['import_source'],
$importItem['l10n_parent'],
true
);
if (!empty($parentNews)) {
$news = $this->initializeNewsRecord($importItem);
$this->hydrateNewsRecord($news, $importItem, $importItemOverwrite);
$news->setSysLanguageUid($importItem['sys_language_uid']);
$news->setL10nParent($parentNews['uid']);
}
}
/**
* Get an existing items from the references that matches the file
*
* @param ObjectStorage $items
* @param \TYPO3\CMS\Core\Resource\File $file
*
* @return bool|FileReference
*/
protected function getIfFalRelationIfAlreadyExists(
ObjectStorage $items,
File $file
) {
$result = false;
if ($items->count() !== 0) {
/** @var $item FileReference */
foreach ($items as $item) {
// only check already persisted items
if ($item->getFileUid() === (int)$file->getUid()
||
($item->getUid() &&
$item->getOriginalResource()->getName() === $file->getName() &&
$item->getOriginalResource()->getSize() === (int)$file->getSize())
) {
$result = $item;
break;
}
}
}
return $result;
}
/**
* Get an existing related link object
*
* @param News $news
* @param string $uri
* @return bool|Link
*/
protected function getRelatedLinkIfAlreadyExists(News $news, $uri)
{
$result = false;
$links = $news->getRelatedLinks();
if (!empty($links) && $links->count() !== 0) {
foreach ($links as $link) {
if ($link->getUri() === $uri) {
$result = $link;
break;
}
}
}
return $result;
}
}