From 0a56a5d4823d74cedf39cb11ec6d85a1d6793f58 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 5 Jul 2022 14:08:14 +0200 Subject: [PATCH] Integrate destination data features Import alongside of categories. Use TYPO3 sys_category again. --- Classes/Controller/DateController.php | 3 +- Classes/Domain/Model/Category.php | 31 +- Classes/Domain/Model/Dto/DateDemand.php | 25 + Classes/Domain/Model/Event.php | 25 + Classes/Domain/Model/Import.php | 25 + .../Domain/Repository/CategoryRepository.php | 36 +- Classes/Domain/Repository/DateRepository.php | 17 + .../Service/DestinationDataImportService.php | 66 +-- .../CategoriesAssignment.php | 65 +++ .../CategoriesAssignment/Import.php | 60 ++ .../tx_events_domain_model_event.php | 13 + .../TCA/tx_events_domain_model_event.php | 10 +- .../TCA/tx_events_domain_model_import.php | 32 +- Documentation/Changelog/2.5.0.rst | 8 + .../Private/Language/locallang_csh_import.xlf | 15 + .../ImportsFeaturesAddsNewFeatures.csv | 29 + .../Fixtures/FeaturesImportConfiguration.xml | 43 ++ .../Fixtures/ResponseWithFeatures.json | 517 ++++++++++++++++++ .../ImportsFeaturesTest.php | 39 ++ Tests/Unit/Domain/Model/CategoryTest.php | 51 ++ .../Unit/Domain/Model/Dto/DateDemandTest.php | 24 + Tests/Unit/Domain/Model/EventTest.php | 63 +++ Tests/Unit/Domain/Model/ImportTest.php | 51 +- ext_tables.sql | 4 + 24 files changed, 1188 insertions(+), 64 deletions(-) create mode 100644 Classes/Service/DestinationDataImportService/CategoriesAssignment.php create mode 100644 Classes/Service/DestinationDataImportService/CategoriesAssignment/Import.php create mode 100644 Tests/Functional/Import/DestinationDataTest/Assertions/ImportsFeaturesAddsNewFeatures.csv create mode 100644 Tests/Functional/Import/DestinationDataTest/Fixtures/FeaturesImportConfiguration.xml create mode 100644 Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithFeatures.json create mode 100644 Tests/Functional/Import/DestinationDataTest/ImportsFeaturesTest.php create mode 100644 Tests/Unit/Domain/Model/CategoryTest.php create mode 100644 Tests/Unit/Domain/Model/EventTest.php diff --git a/Classes/Controller/DateController.php b/Classes/Controller/DateController.php index 1ff0745..7ae81e0 100644 --- a/Classes/Controller/DateController.php +++ b/Classes/Controller/DateController.php @@ -140,7 +140,8 @@ class DateController extends AbstractController 'search' => $search, 'demand' => DateDemand::createFromRequestValues($arguments, $this->settings), 'regions' => $this->regionRepository->findAll(), - 'categories' => $this->categoryRepository->findAllCurrentlyAssigned(), + 'categories' => $this->categoryRepository->findAllCurrentlyAssigned($this->settings['uids']['categoriesParent'] ?? 0, 'categories'), + 'features' => $this->categoryRepository->findAllCurrentlyAssigned($this->settings['uids']['featuresParent'] ?? 0, 'features'), ]); } diff --git a/Classes/Domain/Model/Category.php b/Classes/Domain/Model/Category.php index 1b1412e..35be808 100644 --- a/Classes/Domain/Model/Category.php +++ b/Classes/Domain/Model/Category.php @@ -2,29 +2,12 @@ namespace Wrm\Events\Domain\Model; -/* - * Copyright (C) 2021 Daniel Siepmann - * - * 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. - */ - use TYPO3\CMS\Extbase\Domain\Model\Category as ExtbaseCategory; /** * Extend original model to include furher properties. + * + * Used for Plugins and Import. */ class Category extends ExtbaseCategory { @@ -33,8 +16,18 @@ class Category extends ExtbaseCategory */ protected $sorting = 0; + /** + * @var bool + */ + protected $hidden = false; + public function getSorting(): int { return $this->sorting; } + + public function hide(): void + { + $this->hidden = true; + } } diff --git a/Classes/Domain/Model/Dto/DateDemand.php b/Classes/Domain/Model/Dto/DateDemand.php index c7a9259..a4c1440 100644 --- a/Classes/Domain/Model/Dto/DateDemand.php +++ b/Classes/Domain/Model/Dto/DateDemand.php @@ -26,6 +26,11 @@ class DateDemand */ protected $userCategories = []; + /** + * @var int[] + */ + protected $features = []; + /** * @var bool */ @@ -127,6 +132,10 @@ class DateDemand $instance->setUserCategories($submittedValues['userCategories']); } + if (is_array($submittedValues['features'])) { + $instance->setFeatures($submittedValues['features']); + } + $instance->setSortBy($settings['sortByDate'] ?? ''); $instance->setSortOrder($settings['sortOrder'] ?? ''); $instance->setQueryCallback($settings['queryCallback'] ?? ''); @@ -184,6 +193,22 @@ class DateDemand $this->categories = $categories; } + /** + * @param int[] $categories + */ + public function setFeatures(array $categories): void + { + $this->features = array_map('intval', $categories); + } + + /** + * @return int[] + */ + public function getFeatures(): array + { + return $this->features; + } + public function getIncludeSubCategories(): bool { return $this->includeSubCategories; diff --git a/Classes/Domain/Model/Event.php b/Classes/Domain/Model/Event.php index b7b28e2..8a3ab2e 100644 --- a/Classes/Domain/Model/Event.php +++ b/Classes/Domain/Model/Event.php @@ -155,6 +155,11 @@ class Event extends AbstractEntity */ protected $categories; + /** + * @var ObjectStorage + */ + protected $features; + /** * @var ObjectStorage */ @@ -198,6 +203,7 @@ class Event extends AbstractEntity $this->images = new ObjectStorage(); $this->dates = new ObjectStorage(); $this->categories = new ObjectStorage(); + $this->features = new ObjectStorage(); $this->partner = new ObjectStorage(); $this->referencesEvents = new ObjectStorage(); } @@ -547,6 +553,25 @@ class Event extends AbstractEntity $this->categories = $categories; } + public function getFeatures(): array + { + $features = $this->features->toArray(); + + usort($features, function (Category $catA, Category $catB) { + return $catA->getSorting() <=> $catB->getSorting(); + }); + + return $features; + } + + /** + * @param ObjectStorage $features + */ + public function setFeatures(ObjectStorage $features): void + { + $this->features = $features; + } + public function setLanguageUid(int $languageUid): void { $this->_languageUid = $languageUid; diff --git a/Classes/Domain/Model/Import.php b/Classes/Domain/Model/Import.php index 95b13b7..045af84 100644 --- a/Classes/Domain/Model/Import.php +++ b/Classes/Domain/Model/Import.php @@ -31,6 +31,16 @@ class Import extends AbstractDomainObject */ protected $categoryParent; + /** + * @var int + */ + protected $featuresPid; + + /** + * @var Category|null + */ + protected $featuresParent; + /** * @var Region|null */ @@ -53,6 +63,8 @@ class Import extends AbstractDomainObject string $restSearchQuery = '', int $categoriesPid = 0, ?Category $categoryParent = null, + int $featuresPid = 0, + ?Category $featuresParent = null, ?Region $region = null ) { $this->filesFolder = $filesFolder; @@ -61,6 +73,9 @@ class Import extends AbstractDomainObject $this->categoriesPid = $categoriesPid; $this->categoryParent = $categoryParent; + $this->featuresPid = $featuresPid; + $this->featuresParent = $featuresParent; + $this->restExperience = $restExperience; $this->restSearchQuery = $restSearchQuery; @@ -87,6 +102,16 @@ class Import extends AbstractDomainObject return $this->categoryParent; } + public function getFeaturesPid(): int + { + return $this->featuresPid; + } + + public function getFeaturesParent(): ?Category + { + return $this->featuresParent; + } + public function getRegion(): ?Region { return $this->region; diff --git a/Classes/Domain/Repository/CategoryRepository.php b/Classes/Domain/Repository/CategoryRepository.php index d42155e..ed2bbd7 100644 --- a/Classes/Domain/Repository/CategoryRepository.php +++ b/Classes/Domain/Repository/CategoryRepository.php @@ -54,8 +54,10 @@ class CategoryRepository extends Repository /** * @return array */ - public function findAllCurrentlyAssigned(): array - { + public function findAllCurrentlyAssigned( + int $parentUid = 0, + string $relation = 'categories' + ): array { $qb = $this->connectionPool->getQueryBuilderForTable('tx_events_domain_model_event'); $qb->select('category.*'); $qb->from('tx_events_domain_model_event', 'event'); @@ -66,7 +68,7 @@ class CategoryRepository extends Repository 'mm', 'event.uid = mm.uid_foreign' . ' AND mm.tablenames = ' . $qb->createNamedParameter('tx_events_domain_model_event') - . ' AND mm.fieldname = ' . $qb->createNamedParameter('categories') + . ' AND mm.fieldname = ' . $qb->createNamedParameter($relation) ); $qb->leftJoin( @@ -75,10 +77,13 @@ class CategoryRepository extends Repository 'category', 'category.uid = mm.uid_local' . ' AND mm.tablenames = ' . $qb->createNamedParameter('tx_events_domain_model_event') - . ' AND mm.fieldname = ' . $qb->createNamedParameter('categories') + . ' AND mm.fieldname = ' . $qb->createNamedParameter($relation) ); $qb->where($qb->expr()->neq('category.uid', $qb->createNamedParameter(0))); + if ($parentUid > 0) { + $qb->andWhere($qb->expr()->eq('category.parent', $qb->createNamedParameter($parentUid))); + } $qb->orderBy('category.title', 'asc'); $qb->groupBy('category.uid'); @@ -87,4 +92,27 @@ class CategoryRepository extends Repository $qb->execute()->fetchAll() ); } + + public function findOneForImport( + Category $parentCategory, + int $pid, + string $title + ): ?Category { + $query = $this->createQuery(); + + $query->getQuerySettings()->setStoragePageIds([$pid]); + // Necessary as enableFieldsToBeIgnored would not be respected. + // Both combined lead to only ignoring defined enable fields. + $query->getQuerySettings()->setIgnoreEnableFields(true); + $query->getQuerySettings()->setEnableFieldsToBeIgnored(['disabled']); + + $query->matching($query->logicalAnd([ + $query->equals('parent', $parentCategory), + $query->equals('title', $title) + ])); + + $query->setLimit(1); + + return $query->execute()->getFirst(); + } } diff --git a/Classes/Domain/Repository/DateRepository.php b/Classes/Domain/Repository/DateRepository.php index 221c330..b95211c 100644 --- a/Classes/Domain/Repository/DateRepository.php +++ b/Classes/Domain/Repository/DateRepository.php @@ -60,6 +60,10 @@ class DateRepository extends Repository } } + if ($demand->getFeatures() !== []) { + $constraints['features'] = $this->createFeaturesConstraint($query, $demand); + } + if ($demand->getRegion() !== '') { $constraints['region'] = $query->equals('event.region', $demand->getRegion()); } @@ -183,6 +187,19 @@ class DateRepository extends Repository return $constraints; } + private function createFeaturesConstraint( + QueryInterface $query, + DateDemand $demand + ): ConstraintInterface { + $constraints = []; + + foreach ($demand->getFeatures() as $feature) { + $constraints[] = $query->contains('event.features', $feature); + } + + return $query->logicalAnd($constraints); + } + public function findSearchWord(string $search): array { $connection = GeneralUtility::makeInstance(ConnectionPool::class) diff --git a/Classes/Service/DestinationDataImportService.php b/Classes/Service/DestinationDataImportService.php index dd96be5..75285ff 100644 --- a/Classes/Service/DestinationDataImportService.php +++ b/Classes/Service/DestinationDataImportService.php @@ -27,6 +27,8 @@ use Wrm\Events\Domain\Repository\CategoryRepository; use Wrm\Events\Domain\Repository\DateRepository; use Wrm\Events\Domain\Repository\EventRepository; use Wrm\Events\Domain\Repository\OrganizerRepository; +use Wrm\Events\Service\DestinationDataImportService\CategoriesAssignment; +use Wrm\Events\Service\DestinationDataImportService\CategoriesAssignment\Import as CategoryImport; use Wrm\Events\Service\DestinationDataImportService\DataFetcher; use Wrm\Events\Service\DestinationDataImportService\DatesFactory; @@ -62,11 +64,6 @@ class DestinationDataImportService */ private $dateRepository; - /** - * @var CategoryRepository - */ - private $sysCategoriesRepository; - /** * @var MetaDataRepository */ @@ -97,40 +94,45 @@ class DestinationDataImportService */ private $datesFactory; + /** + * @var CategoriesAssignment + */ + private $categoriesAssignment; + /** * ImportService constructor. * @param EventRepository $eventRepository * @param OrganizerRepository $organizerRepository * @param DateRepository $dateRepository - * @param CategoryRepository $sysCategoriesRepository * @param MetaDataRepository $metaDataRepository * @param ConfigurationManager $configurationManager * @param PersistenceManager $persistenceManager * @param ObjectManager $objectManager * @param DataFetcher $dataFetcher + * @param CategoriesAssignment $categoriesAssignment */ public function __construct( EventRepository $eventRepository, OrganizerRepository $organizerRepository, DateRepository $dateRepository, - CategoryRepository $sysCategoriesRepository, MetaDataRepository $metaDataRepository, ConfigurationManager $configurationManager, PersistenceManager $persistenceManager, ObjectManager $objectManager, DataFetcher $dataFetcher, - DatesFactory $datesFactory + DatesFactory $datesFactory, + CategoriesAssignment $categoriesAssignment ) { $this->eventRepository = $eventRepository; $this->organizerRepository = $organizerRepository; $this->dateRepository = $dateRepository; - $this->sysCategoriesRepository = $sysCategoriesRepository; $this->metaDataRepository = $metaDataRepository; $this->configurationManager = $configurationManager; $this->persistenceManager = $persistenceManager; $this->objectManager = $objectManager; $this->dataFetcher = $dataFetcher; $this->datesFactory = $datesFactory; + $this->categoriesAssignment = $categoriesAssignment; } public function import( @@ -224,6 +226,11 @@ class DestinationDataImportService $this->setCategories($event['categories']); } + // Set Features + if ($event['features']) { + $this->setFeatures($event['features']); + } + // Set Organizer if ($event['addresses'] ?? false) { $this->setOrganizer($event['addresses']); @@ -262,31 +269,27 @@ class DestinationDataImportService return 0; } - /** - * - * @param array $categories - */ private function setCategories(array $categories): void { - $sysParentCategory = $this->import->getCategoryParent(); - if (!$sysParentCategory instanceof Category) { - return; - } + $categories = $this->categoriesAssignment->getCategories(new CategoryImport( + $this->import->getCategoryParent(), + $this->import->getCategoriesPid(), + $categories + )); - foreach ($categories as $categoryTitle) { - $tmpSysCategory = $this->sysCategoriesRepository->findOneByTitle($categoryTitle); - if (!$tmpSysCategory) { - $this->logger->info('Creating new category: ' . $categoryTitle); - $tmpSysCategory = $this->objectManager->get(Category::class); - $tmpSysCategory->setTitle($categoryTitle); - $tmpSysCategory->setParent($sysParentCategory); - $tmpSysCategory->setPid($this->import->getCategoriesPid()); - $this->sysCategoriesRepository->add($tmpSysCategory); - $this->tmpCurrentEvent->addCategory($tmpSysCategory); - } else { - $this->tmpCurrentEvent->addCategory($tmpSysCategory); - } - } + $this->tmpCurrentEvent->setCategories($categories); + } + + private function setFeatures(array $features): void + { + $features = $this->categoriesAssignment->getCategories(new CategoryImport( + $this->import->getFeaturesParent(), + $this->import->getFeaturesPid(), + $features, + true + )); + + $this->tmpCurrentEvent->setFeatures($features); } private function setDates( @@ -456,7 +459,6 @@ class DestinationDataImportService $event = $this->objectManager->get(Event::class); // Create event and persist $event->setGlobalId($globalId); - $event->setCategories(new ObjectStorage()); $this->eventRepository->add($event); $this->persistenceManager->persistAll(); $this->logger->info( diff --git a/Classes/Service/DestinationDataImportService/CategoriesAssignment.php b/Classes/Service/DestinationDataImportService/CategoriesAssignment.php new file mode 100644 index 0000000..333afb4 --- /dev/null +++ b/Classes/Service/DestinationDataImportService/CategoriesAssignment.php @@ -0,0 +1,65 @@ +repository = $repository; + } + + /** + * @return ObjectStorage + */ + public function getCategories( + Import $import + ): ObjectStorage { + $categories = new ObjectStorage(); + + if ($import->getParentCategory() === null) { + return $categories; + } + + foreach ($import->getCategoryTitles() as $categoryTitle) { + $category = $this->repository->findOneForImport( + $import->getParentCategory(), + $import->getPid(), + $categoryTitle + ); + + if (!$category instanceof Category) { + $category = new Category(); + $category->setParent($import->getParentCategory()); + $category->setPid($import->getPid()); + $category->setTitle($categoryTitle); + if ($import->getHideByDefault()) { + $category->hide(); + } + $this->repository->add($category); + } + + $categories->attach($category); + } + + return $categories; + } +} diff --git a/Classes/Service/DestinationDataImportService/CategoriesAssignment/Import.php b/Classes/Service/DestinationDataImportService/CategoriesAssignment/Import.php new file mode 100644 index 0000000..8daa13b --- /dev/null +++ b/Classes/Service/DestinationDataImportService/CategoriesAssignment/Import.php @@ -0,0 +1,60 @@ +parentCategory = $parentCategory; + $this->pid = $pid; + $this->categoryTitles = $categoryTitles; + $this->hideByDefault = $hideByDefault; + } + + public function getParentCategory(): ?Category + { + return $this->parentCategory; + } + + public function getPid(): int + { + return $this->pid; + } + + public function getCategoryTitles(): array + { + return $this->categoryTitles; + } + + public function getHideByDefault(): bool + { + return $this->hideByDefault; + } +} diff --git a/Configuration/TCA/Overrides/tx_events_domain_model_event.php b/Configuration/TCA/Overrides/tx_events_domain_model_event.php index 9cd4456..e6980e5 100644 --- a/Configuration/TCA/Overrides/tx_events_domain_model_event.php +++ b/Configuration/TCA/Overrides/tx_events_domain_model_event.php @@ -13,4 +13,17 @@ ] ] ); + + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::makeCategorizable( + $extKey, + $table, + 'features', + [ + 'label' => 'Features', + 'fieldConfiguration' => [ + 'minitems' => 0, + 'multiple' => true, + ] + ] + ); })('events', 'tx_events_domain_model_event'); diff --git a/Configuration/TCA/tx_events_domain_model_event.php b/Configuration/TCA/tx_events_domain_model_event.php index 0029a85..4e57e97 100644 --- a/Configuration/TCA/tx_events_domain_model_event.php +++ b/Configuration/TCA/tx_events_domain_model_event.php @@ -60,6 +60,7 @@ return [ region, partner, categories, + features, references_events, pages, --div--;' . $l10nPath . ':tx_events_domain_model_event.tabs.media, @@ -457,12 +458,9 @@ return [ 'categories' => [ 'exclude' => true, - 'label' => $l10nPath . ':tx_events_domain_model_event.categories', - 'config' => [ - 'type' => 'input', - 'size' => 4, - 'eval' => 'int' - ] + ], + 'features' => [ + 'exclude' => true, ], 'dates' => [ diff --git a/Configuration/TCA/tx_events_domain_model_import.php b/Configuration/TCA/tx_events_domain_model_import.php index 54c1cdd..603ebe8 100644 --- a/Configuration/TCA/tx_events_domain_model_import.php +++ b/Configuration/TCA/tx_events_domain_model_import.php @@ -18,7 +18,7 @@ return [ ], 'types' => [ '1' => [ - 'showitem' => 'title, hidden, --div--;LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.div.typo3, --palette--;;typo3_storage, --palette--;;categories, --palette--;;relations, --div--;LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.div.rest, rest_experience, rest_search_query' + 'showitem' => 'title, hidden, --div--;LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.div.typo3, --palette--;;typo3_storage, --palette--;;categories, --palette--;;features,--palette--;;relations, --div--;LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.div.rest, rest_experience, rest_search_query' ], ], 'palettes' => [ @@ -30,6 +30,10 @@ return [ 'label' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.palette.categories', 'showitem' => 'category_parent, categories_pid' ], + 'features' => [ + 'label' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.palette.features', + 'showitem' => 'features_parent, features_pid, ' + ], 'relations' => [ 'label' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.palette.relations', 'showitem' => 'region' @@ -113,6 +117,32 @@ return [ 'minitems' => 0, ], ], + 'features_pid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.features_pid', + 'description' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.features_pid.description', + 'config' => [ + 'type' => 'group', + 'internal_type' => 'db', + 'allowed' => 'pages', + 'size' => 1, + 'maxitems' => 1, + 'minitems' => 0, + ], + ], + 'features_parent' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.features_parent', + 'description' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.features_parent.description', + 'config' => [ + 'type' => 'group', + 'internal_type' => 'db', + 'allowed' => 'sys_category', + 'size' => 1, + 'maxitems' => 1, + 'minitems' => 0, + ], + ], 'files_folder' => [ 'exclude' => true, 'label' => 'LLL:EXT:events/Resources/Private/Language/locallang_csh_import.xlf:tx_events_domain_model_import.files_folder', diff --git a/Documentation/Changelog/2.5.0.rst b/Documentation/Changelog/2.5.0.rst index c2be87c..b579167 100644 --- a/Documentation/Changelog/2.5.0.rst +++ b/Documentation/Changelog/2.5.0.rst @@ -31,6 +31,14 @@ Features * ``getEndsOnSameDay()`` +* Add destination data "Features" to events. + Features are again TYPO3 categories with different parent as existing categories. + New features are hidden by default but can be activated within TYPO3. + That allows to fetch all within controller and provide them as filter. + But editors actively need to enable them for filtering. + + It is possible to add them to user submitted filter. + Fixes ----- diff --git a/Resources/Private/Language/locallang_csh_import.xlf b/Resources/Private/Language/locallang_csh_import.xlf index 6419e68..8d81c0c 100644 --- a/Resources/Private/Language/locallang_csh_import.xlf +++ b/Resources/Private/Language/locallang_csh_import.xlf @@ -21,6 +21,9 @@ Categories + + Features + Title @@ -48,6 +51,18 @@ Existing TYPO3 category used as parent for all categories created during import. + + Features Storage Folder + + + TYPO3 page to use for storing imported features. + + + Parent Features Category + + + Existing TYPO3 category used as parent for all features created during import. + Folder diff --git a/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsFeaturesAddsNewFeatures.csv b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsFeaturesAddsNewFeatures.csv new file mode 100644 index 0000000..36a05a3 --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsFeaturesAddsNewFeatures.csv @@ -0,0 +1,29 @@ +"tx_events_domain_model_event",,,,,, +,"uid","pid","features",,, +,1,2,1,,, +,2,2,1,,, +,3,2,0,,, +"sys_category",,,,,, +,"uid","pid","title","parent","hidden", +,1,2,"Top Category",0,0, +,2,2,"Event Category Parent",1,0, +,4,2,"Event Feature Parent",1,0, +,5,3,"vorhandenes Feature",4,0, +,6,3,"Barrierefrei",4,1, +,7,3,"Zielgruppe Jugendliche",4,1, +,8,3,"Karten an der Abendkasse",4,1, +,9,3,"Ausreichende Lüftung",4,1, +,10,3,"Beachtung der Hygienehinweise",4,1, +,11,3,"neues Feature",4,1, +"sys_category_record_mm",,,,,, +,"uid_local","uid_foreign","tablenames","fieldname","sorting","sorting_foreign" +,5,1,"tx_events_domain_model_event","features",0,1 +,6,1,"tx_events_domain_model_event","features",0,2 +,7,1,"tx_events_domain_model_event","features",0,3 +,8,1,"tx_events_domain_model_event","features",0,4 +,9,1,"tx_events_domain_model_event","features",0,5 +,10,1,"tx_events_domain_model_event","features",0,6 +,5,2,"tx_events_domain_model_event","features",0,1 +,6,2,"tx_events_domain_model_event","features",0,2 +,7,2,"tx_events_domain_model_event","features",0,3 +,11,2,"tx_events_domain_model_event","features",0,4 diff --git a/Tests/Functional/Import/DestinationDataTest/Fixtures/FeaturesImportConfiguration.xml b/Tests/Functional/Import/DestinationDataTest/Fixtures/FeaturesImportConfiguration.xml new file mode 100644 index 0000000..2fcbaaa --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Fixtures/FeaturesImportConfiguration.xml @@ -0,0 +1,43 @@ + + + + 1 + 2 + Example for test + 2 + 3 + 4 + + + + 2 + 3 + Features Storage + 254 + + + + 1 + 2 + Top Category + + + 2 + 2 + Event Category Parent + 1 + + + 4 + 2 + Event Feature Parent + 1 + + + 5 + 3 + vorhandenes Feature + 4 + 0 + + diff --git a/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithFeatures.json b/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithFeatures.json new file mode 100644 index 0000000..ec1e3a4 --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/Fixtures/ResponseWithFeatures.json @@ -0,0 +1,517 @@ +{ + "status": "OK", + "count": 3, + "overallcount": 50, + "channels": [], + "facetGroups": [], + "items": [ + { + "global_id": "e_100347853", + "id": "100347853", + "title": "Allerlei Weihnachtliches (Heute mit Johannes Geißer)", + "type": "Event", + "categories": [ + "Weihnachten" + ], + "texts": [ + { + "rel": "details", + "type": "text/html", + "value": "Die Lichter sind entzündet, die Plätzchen duften, man rückt endlich wieder näher zusammen und lauscht den Geschichten. Vier Schauspieler*innen unseres Theaters überraschen mit ihren weihnachtlichen Texten, die sie für uns ausgewählt haben. Dazu plaudern sie über persönliche Anekdoten und erinnern sich an ihre schönsten und verrücktesten Weihnachtsfeste. Und da der Genuss in der Vorweihnachtszeit nicht fehlen darf, wird an jedem Adventssonntag eine andere weihnachtliche Spezialität serviert.
Eintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)
Um Voranmeldung unter 03672-486470 oder schillerhaus@rudolstadt.de wird gebeten.
Es gilt die 2G-PLUS-Regel. 
" + }, + { + "rel": "details", + "type": "text/plain", + "value": "Die Lichter sind entzündet, die Plätzchen duften, man rückt endlich wieder näher zusammen und lauscht den Geschichten. Vier Schauspieler*innen unseres Theaters überraschen mit ihren weihnachtlichen Texten, die sie für uns ausgewählt haben. Dazu plaudern sie über persönliche Anekdoten und erinnern sich an ihre schönsten und verrücktesten Weihnachtsfeste. Und da der Genuss in der Vorweihnachtszeit nicht fehlen darf, wird an jedem Adventssonntag eine andere weihnachtliche Spezialität serviert.\nEintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)\nUm Voranmeldung unter 03672-486470 oder schillerhaus@rudolstadt.de wird gebeten.\nEs gilt die 2G-PLUS-Regel." + }, + { + "rel": "teaser", + "type": "text/html" + }, + { + "rel": "teaser", + "type": "text/plain" + } + ], + "country": "Deutschland", + "areas": [ + "Rudolstadt und Umgebung" + ], + "city": "Rudolstadt", + "zip": "07407", + "street": "Schillerstraße 25", + "phone": "+ 49 3672 / 486470", + "fax": "+ 49 3672 / 486475", + "web": "http://www.schillerhaus.rudolstadt.de/", + "email": "schillerhaus@rudolstadt.de", + "author": "support@hubermedia.de", + "geo": { + "main": { + "latitude": 50.720971023258805, + "longitude": 11.335229873657227 + }, + "entry": [], + "attributes": [] + }, + "ratings": [ + { + "type": "eT4", + "value": 40.0 + }, + { + "type": "order", + "value": 99.0001 + } + ], + "cuisine_types": [], + "payment": [], + "media_objects": [ + ], + "keywords": [], + "timeIntervals": [ + { + "weekdays": [], + "start": "2099-12-19T15:00:00+01:00", + "end": "2099-12-19T16:30:00+01:00", + "tz": "Europe/Berlin", + "interval": 1 + } + ], + "kitchenTimeIntervals": [], + "deliveryTimeIntervals": [], + "numbers": [], + "name": "Schillerhaus Rudolstadt", + "attributes": [ + { + "key": "VO_Id", + "value": "100050775" + }, + { + "key": "VO_CategoryName", + "value": "POI" + }, + { + "key": "VA_Id", + "value": "100050775" + }, + { + "key": "VA_CategoryName", + "value": "POI" + }, + { + "key": "interval_first_match_start", + "value": "2099-12-19T15:00:00+01" + }, + { + "key": "interval_first_match_end", + "value": "2099-12-19T16:30:00+01" + }, + { + "key": "interval_match_count", + "value": "1" + } + ], + "features": [ + "vorhandenes Feature", + "Barrierefrei", + "Zielgruppe Jugendliche", + "Karten an der Abendkasse", + "Ausreichende Lüftung", + "Beachtung der Hygienehinweise" + ], + "addresses": [ + { + "name": "Städtetourismus in Thüringen e.V.", + "city": "Weimar", + "zip": "99423", + "street": "UNESCO-Platz 1", + "phone": "+49 (3643) 745 314", + "web": "http://www.thueringer-staedte.de", + "email": "verein@thueringer-staedte.de", + "rel": "author" + }, + { + "name": "Städtetourismus in Thüringen\" e.V.", + "web": "http://www.thueringer-staedte.de", + "email": "verein@thueringer-staedte.de", + "rel": "organisation" + }, + { + "name": "Schillerhaus Rudolstadt", + "city": "Rudolstadt", + "zip": "07407", + "street": "Schillerstraße 25", + "phone": "+ 49 3672 / 486470", + "fax": "+ 49 3672 / 486475", + "web": "http://schillerhaus.rudolstadt.de", + "email": "schillerhaus@rudolstadt.de", + "rel": "organizer" + } + ], + "created": "2099-10-31T12:29:00+00:00", + "changed": "2099-12-14T08:29:00+00:00", + "source": { + "url": "http://destination.one/", + "value": "destination.one" + }, + "company": "", + "district": "", + "postoffice": "", + "phone2": "", + "seasons": [], + "subitems": [], + "hyperObjects": [] + }, + { + "global_id": "e_100354481", + "id": "100354481", + "title": "Tüftlerzeit", + "type": "Event", + "categories": [ + "Kinder" + ], + "texts": [ + { + "rel": "details", + "type": "text/html", + "value": "Die Tüftlerzeit wird dieses Mal ein weihnachtliches Angebot bereithalten. Alle kleinen Tüftler dürfen gespannt sein.
Voranmeldung über: kinderbibliothek@rudolstadt.de oder 03672-486420

Bitte beachten Sie die derzeit geltenden Zugangsregeln." + }, + { + "rel": "details", + "type": "text/plain", + "value": "Die Tüftlerzeit wird dieses Mal ein weihnachtliches Angebot bereithalten. Alle kleinen Tüftler dürfen gespannt sein.\nVoranmeldung über: kinderbibliothek@rudolstadt.de oder 03672-486420\n\nBitte beachten Sie die derzeit geltenden Zugangsregeln." + }, + { + "rel": "teaser", + "type": "text/html" + }, + { + "rel": "teaser", + "type": "text/plain" + } + ], + "country": "Deutschland", + "areas": [ + "Rudolstadt und Umgebung" + ], + "city": "Rudolstadt", + "zip": "07407", + "street": "Schulplatz 13", + "phone": "0 36 72 - 48 64 20", + "fax": "0 36 72 - 48 64 30", + "web": "http://www.stadtbibliothek-rudolstadt.de/", + "email": "stadtbibliothek@rudolstadt.de", + "author": "support@hubermedia.de", + "geo": { + "main": { + "latitude": 50.720835175055917, + "longitude": 11.342568397521973 + }, + "entry": [], + "attributes": [] + }, + "ratings": [ + { + "type": "eT4", + "value": 40.0 + }, + { + "type": "order", + "value": 99.0001 + } + ], + "cuisine_types": [], + "payment": [], + "media_objects": [ + ], + "keywords": [], + "timeIntervals": [ + { + "weekdays": [], + "start": "2099-12-16T15:00:00+01:00", + "end": "2099-12-16T16:30:00+01:00", + "tz": "Europe/Berlin", + "interval": 1 + }, + { + "weekdays": [], + "start": "2099-04-01T11:00:00+02:00", + "end": "2099-04-01T13:00:00+02:00", + "repeatUntil": "2099-04-03T00:00+02:00", + "tz": "Europe/Berlin", + "freq": "Daily", + "interval": 1 + }, + { + "weekdays": [], + "start": "2099-02-17T15:00:00+01:00", + "end": "2099-02-17T17:00:00+01:00", + "tz": "Europe/Berlin", + "interval": 1 + } + ], + "kitchenTimeIntervals": [], + "deliveryTimeIntervals": [], + "numbers": [], + "name": "Stadtbibliothek Rudolstadt", + "attributes": [ + { + "key": "VO_Id", + "value": "100042570" + }, + { + "key": "VO_CategoryName", + "value": "POI" + }, + { + "key": "VA_Id", + "value": "100042570" + }, + { + "key": "VA_CategoryName", + "value": "POI" + }, + { + "key": "interval_first_match_start", + "value": "2099-12-16T15:00:00+01" + }, + { + "key": "interval_first_match_end", + "value": "2099-12-16T16:30:00+01" + }, + { + "key": "interval_match_count", + "value": "3" + }, + { + "key": "interval_last_match_start", + "value": "2022-02-17T15:00:00+01" + }, + { + "key": "interval_last_match_end", + "value": "2022-02-17T17:00:00+01" + } + ], + "features": [ + "vorhandenes Feature", + "Barrierefrei", + "Zielgruppe Jugendliche", + "neues Feature" + ], + "addresses": [ + { + "name": "Städtetourismus in Thüringen e.V.", + "city": "Weimar", + "zip": "99423", + "street": "UNESCO-Platz 1", + "phone": "+49 (3643) 745 314", + "web": "http://www.thueringer-staedte.de", + "email": "verein@thueringer-staedte.de", + "rel": "author" + }, + { + "name": "Städtetourismus in Thüringen\" e.V.", + "web": "http://www.thueringer-staedte.de", + "email": "verein@thueringer-staedte.de", + "rel": "organisation" + }, + { + "name": "Stadtbibliothek Rudolstadt", + "city": "Rudolstadt", + "zip": "07407", + "street": "Schulplatz 13", + "phone": "0 36 72 - 48 64 20", + "fax": "0 36 72 - 48 64 30", + "web": "http://www.stadtbibliothek-rudolstadt.de ", + "email": "stadtbibliothek@rudolstadt.de", + "rel": "organizer" + } + ], + "created": "2099-11-10T23:02:00+00:00", + "changed": "2099-12-14T08:28:00+00:00", + "source": { + "url": "http://destination.one/", + "value": "destination.one" + }, + "company": "", + "district": "", + "postoffice": "", + "phone2": "", + "seasons": [], + "subitems": [], + "hyperObjects": [] + }, + { + "global_id": "e_100350503", + "id": "100350503", + "title": "Adventliche Orgelmusik (Orgel: KMD Frank Bettenhausen)", + "type": "Event", + "categories": [ + "Konzerte, Festivals, Show & Tanz", + "Weihnachten" + ], + "texts": [ + { + "rel": "details", + "type": "text/html", + "value": "Immer mittwochs in der Adventszeit spielt Frank Bettenhausen solo und zusammen mit anderen Musikern auf der Steinmeyerorgel aus dem Jahr 1906.  Bekannte Adventslieder, barocke und romantische Kompositionen stehen neben besinnlichen Texten von Pfarrer Johannes-Martin Weiss.

Es gilt die 2G-PLUS-Regel.
" + }, + { + "rel": "details", + "type": "text/plain", + "value": "Immer mittwochs in der Adventszeit spielt Frank Bettenhausen solo und zusammen mit anderen Musikern auf der Steinmeyerorgel aus dem Jahr 1906. Bekannte Adventslieder, barocke und romantische Kompositionen stehen neben besinnlichen Texten von Pfarrer Johannes-Martin Weiss.\n\nEs gilt die 2G-PLUS-Regel." + }, + { + "rel": "teaser", + "type": "text/html" + }, + { + "rel": "teaser", + "type": "text/plain" + } + ], + "country": "Deutschland", + "areas": [ + "Rudolstadt und Umgebung" + ], + "city": "Rudolstadt", + "zip": "07407", + "street": "Caspar-Schulte-Straße", + "phone": "03672 - 48 96 13", + "author": "support@hubermedia.de", + "geo": { + "main": { + "latitude": 50.718688721182531, + "longitude": 11.327333450317383 + }, + "entry": [], + "attributes": [] + }, + "ratings": [ + { + "type": "eT4", + "value": 40.0 + }, + { + "type": "order", + "value": 99.0001 + } + ], + "cuisine_types": [], + "payment": [], + "media_objects": [ + ], + "keywords": [], + "timeIntervals": [ + { + "weekdays": [], + "start": "2099-12-01T19:00:00+01:00", + "end": "2099-12-01T20:00:00+01:00", + "tz": "Europe/Berlin", + "interval": 1 + }, + { + "weekdays": [ + "Saturday", + "Sunday" + ], + "start": "2099-11-02T11:00:00+01:00", + "end": "2099-11-02T13:00:00+01:00", + "repeatUntil": "2099-11-25T13:00:00+01:00", + "tz": "Europe/Berlin", + "freq": "Weekly", + "interval": 1 + }, + { + "weekdays": [], + "start": "2099-12-22T19:00:00+01:00", + "end": "2099-12-22T20:00:00+01:00", + "tz": "Europe/Berlin", + "interval": 1 + } + ], + "kitchenTimeIntervals": [], + "deliveryTimeIntervals": [], + "numbers": [], + "name": "Lutherkirche", + "attributes": [ + { + "key": "VO_Id", + "value": "100118350" + }, + { + "key": "VO_CategoryName", + "value": "POI" + }, + { + "key": "VA_Id", + "value": "100118350" + }, + { + "key": "VA_CategoryName", + "value": "POI" + }, + { + "key": "interval_first_match_start", + "value": "2099-12-15T19:00:00+01" + }, + { + "key": "interval_first_match_end", + "value": "2099-12-15T20:00:00+01" + }, + { + "key": "interval_match_count", + "value": "2" + }, + { + "key": "interval_last_match_start", + "value": "2099-12-22T19:00:00+01" + }, + { + "key": "interval_last_match_end", + "value": "2099-12-22T20:00:00+01" + } + ], + "features": [], + "addresses": [ + { + "name": "Städtetourismus in Thüringen e.V.", + "city": "Weimar", + "zip": "99423", + "street": "UNESCO-Platz 1", + "phone": "+49 (3643) 745 314", + "web": "http://www.thueringer-staedte.de", + "email": "verein@thueringer-staedte.de", + "rel": "author" + }, + { + "name": "Städtetourismus in Thüringen\" e.V.", + "web": "http://www.thueringer-staedte.de", + "email": "verein@thueringer-staedte.de", + "rel": "organisation" + }, + { + "name": "Lutherkirche", + "city": "Rudolstadt", + "zip": "07407", + "street": "Caspar-Schulte-Straße", + "phone": "03672 - 48 96 13", + "rel": "organizer" + } + ], + "created": "2099-11-08T22:15:00+00:00", + "changed": "2099-12-14T08:38:00+00:00", + "source": { + "url": "http://destination.one/", + "value": "destination.one" + }, + "company": "", + "district": "", + "postoffice": "", + "phone2": "", + "seasons": [], + "subitems": [], + "hyperObjects": [] + } + ] +} diff --git a/Tests/Functional/Import/DestinationDataTest/ImportsFeaturesTest.php b/Tests/Functional/Import/DestinationDataTest/ImportsFeaturesTest.php new file mode 100644 index 0000000..c551ab0 --- /dev/null +++ b/Tests/Functional/Import/DestinationDataTest/ImportsFeaturesTest.php @@ -0,0 +1,39 @@ +setUpConfiguration([ + 'restUrl = https://example.com/some-path/', + ]); + $this->importDataSet('EXT:events/Tests/Functional/Import/DestinationDataTest/Fixtures/FeaturesImportConfiguration.xml'); + $this->setUpResponses([ + new Response(200, [], file_get_contents(__DIR__ . '/Fixtures/ResponseWithFeatures.json') ?: ''), + ]); + $tester = $this->executeCommand([ + 'configurationUid' => '1', + ], ImportDestinationDataViaConfigruationCommand::class); + + + $this->assertCSVDataSet('EXT:events/Tests/Functional/Import/DestinationDataTest/Assertions/ImportsFeaturesAddsNewFeatures.csv'); + self::assertFileEquals( + __DIR__ . '/Assertions/EmptyLogFile.txt', + $this->getInstancePath() . '/typo3temp/var/log/typo3_0493d91d8e.log', + 'Logfile was not empty.' + ); + } +} diff --git a/Tests/Unit/Domain/Model/CategoryTest.php b/Tests/Unit/Domain/Model/CategoryTest.php new file mode 100644 index 0000000..b44d188 --- /dev/null +++ b/Tests/Unit/Domain/Model/CategoryTest.php @@ -0,0 +1,51 @@ +_setProperty('sorting', 10); + + self::assertSame(10, $subject->getSorting()); + } + + /** + * @test + */ + public function canHide(): void + { + $subject = new Category(); + + self::assertFalse($subject->_getProperty('hidden')); + + $subject->hide(); + self::assertTrue($subject->_getProperty('hidden')); + } +} diff --git a/Tests/Unit/Domain/Model/Dto/DateDemandTest.php b/Tests/Unit/Domain/Model/Dto/DateDemandTest.php index c116c10..7f536b3 100644 --- a/Tests/Unit/Domain/Model/Dto/DateDemandTest.php +++ b/Tests/Unit/Domain/Model/Dto/DateDemandTest.php @@ -102,6 +102,30 @@ class DateDemandTest extends TestCase ); } + /** + * @test + */ + public function featuresAreSetByRequest(): void + { + $result = DateDemand::createFromRequestValues( + [ + 'features' => [ + '10', '20', + ], + ], + [ + ] + ); + + self::assertSame( + [ + 10, + 20, + ], + $result->getFeatures() + ); + } + /** * @test */ diff --git a/Tests/Unit/Domain/Model/EventTest.php b/Tests/Unit/Domain/Model/EventTest.php new file mode 100644 index 0000000..567acc6 --- /dev/null +++ b/Tests/Unit/Domain/Model/EventTest.php @@ -0,0 +1,63 @@ +_setProperty('sorting', 10); + $feature2 = new Category(); + $feature2->_setProperty('sorting', 5); + + $storage = new ObjectStorage(); + $storage->attach($feature1); + $storage->attach($feature2); + + $subject = new Event(); + $subject->setFeatures($storage); + + self::assertSame([ + $feature2, + $feature1, + ], $subject->getFeatures()); + } + + /** + * @test + */ + public function returnsEmptyFeaturesStorage(): void + { + $subject = new Event(); + $subject->setFeatures(new ObjectStorage()); + + self::assertSame([], $subject->getFeatures()); + } +} diff --git a/Tests/Unit/Domain/Model/ImportTest.php b/Tests/Unit/Domain/Model/ImportTest.php index 9c615b7..264acca 100644 --- a/Tests/Unit/Domain/Model/ImportTest.php +++ b/Tests/Unit/Domain/Model/ImportTest.php @@ -9,7 +9,6 @@ use PHPUnit\Framework\TestCase; use Wrm\Events\Domain\Model\Region; use Wrm\Events\Tests\ProphecyTrait; - /** * @covers \Wrm\Events\Domain\Model\Import */ @@ -89,6 +88,8 @@ class ImportTest extends TestCase '', 0, null, + 0, + null, $region->reveal() ); @@ -161,6 +162,54 @@ class ImportTest extends TestCase ); } + /** + * @test + */ + public function returnsFeaturesPid(): void + { + $folder = $this->prophesize(Folder::class); + + $subject = new Import( + $folder->reveal(), + 0, + '', + '', + 0, + null, + 10 + ); + + self::assertSame( + 10, + $subject->getFeaturesPid() + ); + } + + /** + * @test + */ + public function returnsFeaturesParent(): void + { + $feature = $this->prophesize(Category::class); + $folder = $this->prophesize(Folder::class); + + $subject = new Import( + $folder->reveal(), + 0, + '', + '', + 0, + null, + 0, + $feature->reveal() + ); + + self::assertSame( + $feature->reveal(), + $subject->getFeaturesParent() + ); + } + /** * @test */ diff --git a/ext_tables.sql b/ext_tables.sql index a211caf..e96768d 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -27,6 +27,7 @@ CREATE TABLE tx_events_domain_model_event ( longitude varchar(255) DEFAULT '' NOT NULL, images int(11) unsigned NOT NULL default '0', categories int(11) DEFAULT '0' NOT NULL, + features int(11) DEFAULT '0' NOT NULL, pages text, dates int(11) unsigned DEFAULT '0' NOT NULL, organizer int(11) unsigned DEFAULT '0', @@ -106,6 +107,9 @@ CREATE TABLE tx_events_domain_model_import ( categories_pid int(11) unsigned DEFAULT '0' NOT NULL, category_parent int(11) unsigned DEFAULT '0' NOT NULL, + features_pid int(11) unsigned DEFAULT '0' NOT NULL, + features_parent int(11) unsigned DEFAULT '0' NOT NULL, + region int(11) unsigned DEFAULT '0' NOT NULL, rest_experience varchar(1024) DEFAULT '' NOT NULL,