From 8663d5a75926da9d660d6ee05e70b29bac2a5599 Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Tue, 29 Aug 2023 13:54:16 +0200 Subject: [PATCH] Add support for additional images added via TYPO3 (#112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some installations might need to add further images to records imported from ThüCAT. The records are now extended to support adding images by editors. The images are not touched during import. The images are also ignored during clean ups, the editor is in full control. This feature for now is only added to tourist attractions by default. The feature is implemented in a way that all objects extending the ``WerkraumMedia\ThueCat\Domain\Model\Frontend\Base`` class are usable by adding an ``editorial_images`` field to their table. --- Classes/Domain/Model/Frontend/Media.php | 28 +++ .../DataMapping/AfterObjectThawedHandler.php | 108 +++++++++++ Configuration/Services.yaml | 5 + .../TCA/tx_thuecat_tourist_attraction.php | 12 +- Documentation/Changelog/2.1.0.rst | 14 ++ Resources/Private/Language/locallang_tca.xlf | 6 + .../Fixtures/Frontend/Rendering.typoscript | 9 + .../ContentElement/Accessibility.html | 136 ++++++++++++++ .../Partials/ContentElement/Address.html | 24 +++ .../Partials/ContentElement/Digital.html | 9 + .../Partials/ContentElement/Languages.html | 9 + .../Partials/ContentElement/Museum.html | 9 + .../Partials/ContentElement/Offers.html | 80 +++++++++ .../Partials/ContentElement/Opening.html | 27 +++ .../Partials/ContentElement/Parking.html | 22 +++ .../Partials/ContentElement/Payment.html | 9 + .../Partials/ContentElement/Photography.html | 9 + .../Partials/ContentElement/Sanitation.html | 9 + .../Partials/ContentElement/Service.html | 5 + .../Partials/ContentElement/Traffic.html | 9 + .../ContentElement/TouristAttraction.html | 170 ++++++++++++++++++ .../TouristAttractionWithEditorialImages.xml | 58 ++++++ Tests/Functional/FrontendTest.php | 48 +++-- .../Unit/Domain/Model/Frontend/MediaTest.php | 95 ++++++++++ ext_tables.sql | 1 + 25 files changed, 897 insertions(+), 14 deletions(-) create mode 100644 Classes/Typo3/Extbase/DataMapping/AfterObjectThawedHandler.php create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Accessibility.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Address.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Digital.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Languages.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Museum.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Offers.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Opening.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Parking.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Payment.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Photography.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Sanitation.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Service.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Traffic.html create mode 100644 Tests/Functional/Fixtures/Frontend/Resources/Private/Templates/ContentElement/TouristAttraction.html create mode 100644 Tests/Functional/Fixtures/Frontend/TouristAttractionWithEditorialImages.xml diff --git a/Classes/Domain/Model/Frontend/Media.php b/Classes/Domain/Model/Frontend/Media.php index de7bba9..028e6c2 100644 --- a/Classes/Domain/Model/Frontend/Media.php +++ b/Classes/Domain/Model/Frontend/Media.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace WerkraumMedia\ThueCat\Domain\Model\Frontend; +use TYPO3\CMS\Core\Resource\FileReference; use TYPO3\CMS\Core\Type\TypeInterface; class Media implements TypeInterface @@ -37,6 +38,11 @@ class Media implements TypeInterface */ private $data; + /** + * @var FileReference[] + */ + protected $editorialImages = []; + public function __construct(string $serialized) { $this->serialized = $serialized; @@ -73,6 +79,28 @@ class Media implements TypeInterface }); } + public function getAllImages(): array + { + return array_merge($this->getEditorialImages(), $this->getImages()); + } + + /** + * @return FileReference[] + */ + public function getEditorialImages(): array + { + return $this->editorialImages; + } + + /** + * @internal Only used to set the values while mapping objects. + * @see: AfterObjectThawedHandler + */ + public function setEditorialImages(array $images): void + { + $this->editorialImages = $images; + } + public function __toString(): string { return $this->serialized; diff --git a/Classes/Typo3/Extbase/DataMapping/AfterObjectThawedHandler.php b/Classes/Typo3/Extbase/DataMapping/AfterObjectThawedHandler.php new file mode 100644 index 0000000..e977729 --- /dev/null +++ b/Classes/Typo3/Extbase/DataMapping/AfterObjectThawedHandler.php @@ -0,0 +1,108 @@ + + * + * 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\ThueCat\Typo3\Extbase\DataMapping; + +use TYPO3\CMS\Core\Resource\FileRepository; +use TYPO3\CMS\Extbase\Event\Persistence\AfterObjectThawedEvent; +use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory; +use WerkraumMedia\ThueCat\Domain\Model\Frontend\Base; +use WerkraumMedia\ThueCat\Domain\Model\Frontend\Media; + +/** + * Will extend mapped objects with further info. + * + * E.g. will add editorial images to media property. + */ +class AfterObjectThawedHandler +{ + /** + * @var FileRepository + */ + private $fileRepository; + + /** + * @var DataMapFactory + */ + private $dataMapFactory; + + public function __construct( + FileRepository $fileRepository, + DataMapFactory $dataMapFactory + ) { + $this->fileRepository = $fileRepository; + $this->dataMapFactory = $dataMapFactory; + } + + public function __invoke(AfterObjectThawedEvent $event): void + { + $object = $event->getObject(); + $record = $event->getRecord(); + + if ( + $object instanceof Base + && ($record['editorial_images'] ?? 0) > 0 + ) { + $this->attachEditorialImages($object); + } + } + + private function attachEditorialImages(Base $object): void + { + $uid = $object->getUid(); + if ($uid === null) { + return; + } + + $images = $this->fileRepository->findByRelation( + $this->getTableNameForObject($object), + 'editorial_images', + $uid + ); + if ($images === []) { + return; + } + + $this->getMedia($object)->setEditorialImages($images); + } + + private function getTableNameForObject(Base $object): string + { + return $this->dataMapFactory + ->buildDataMap(get_class($object)) + ->getTableName() + ; + } + + private function getMedia(Base $object): Media + { + $media = $object->getMedia(); + + if (!$media instanceof Media) { + $media = new Media(''); + $object->_setProperty('media', $media); + } + + return $media; + } +} diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index c7357f3..571b69a 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -25,6 +25,11 @@ services: class: 'WerkraumMedia\ThueCat\Service\DateBasedFilter\FilterBasedOnTypo3Context' public: true + WerkraumMedia\ThueCat\Typo3\Extbase\DataMapping\AfterObjectThawedHandler: + tags: + - name: 'event.listener' + event: 'TYPO3\CMS\Extbase\Event\Persistence\AfterObjectThawedEvent' + 'cache.thuecat_fetchdata': class: 'TYPO3\CMS\Core\Cache\Frontend\FrontendInterface' factory: diff --git a/Configuration/TCA/tx_thuecat_tourist_attraction.php b/Configuration/TCA/tx_thuecat_tourist_attraction.php index 49222e9..dd2cf80 100644 --- a/Configuration/TCA/tx_thuecat_tourist_attraction.php +++ b/Configuration/TCA/tx_thuecat_tourist_attraction.php @@ -310,6 +310,16 @@ return (static function (string $extensionKey, string $tableName) { 'readOnly' => true, ], ], + + 'editorial_images' => [ + 'label' => $languagePath . '.editorial_images', + 'l10n_mode' => 'exclude', + 'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig( + 'editorial_images', + [], + $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] + ), + ], ], 'palettes' => [ 'language' => [ @@ -319,7 +329,7 @@ return (static function (string $extensionKey, string $tableName) { ], 'types' => [ '0' => [ - 'showitem' => '--palette--;;language, title, description, slogan, start_of_construction, sanitation, other_service, museum_service, architectural_style, traffic_infrastructure, payment_accepted, digital_offer, photography, pets_allowed, is_accessible_for_free, public_access, available_languages, distance_to_public_transport, opening_hours, special_opening_hours, offers, accessibility_specification, address, url, media, remote_id, --div--;' . $languagePath . '.tab.relations, town, managed_by, parking_facility_near_by', + 'showitem' => '--palette--;;language, title, description, slogan, start_of_construction, sanitation, other_service, museum_service, architectural_style, traffic_infrastructure, payment_accepted, digital_offer, photography, pets_allowed, is_accessible_for_free, public_access, available_languages, distance_to_public_transport, opening_hours, special_opening_hours, offers, accessibility_specification, address, url, media, remote_id, --div--;' . $languagePath . '.tab.relations, town, managed_by, parking_facility_near_by, --div--;' . $languagePath . '.tab.editorial_additions, editorial_images', ], ], ]; diff --git a/Documentation/Changelog/2.1.0.rst b/Documentation/Changelog/2.1.0.rst index c050ced..94af50f 100644 --- a/Documentation/Changelog/2.1.0.rst +++ b/Documentation/Changelog/2.1.0.rst @@ -14,6 +14,15 @@ Features This finally allows to regularly execute imports. This also allows to import from CLI context with differently configured timeouts. +* Add support for additional images added via TYPO3. + Some installations might need to add further images to records imported from ThüCAT. + The records are now extended to support adding images by editors. + The images are not touched during import. + The images are also ignored during clean ups, the editor is in full control. + + This feature for now is only added to tourist attractions by default. + The feature is implemented in a way that all objects extending the ``WerkraumMedia\ThueCat\Domain\Model\Frontend\Base`` class are usable by adding an ``editorial_images`` field to their table. + Fixes ----- @@ -28,6 +37,11 @@ Tasks As this might hint at an issue but most probably is okay, e.g. due to none active language, missing name, etc. +* Separate default templates from templates for testing. + That way we no longer test the delivered templates, but they should not be used anyway. + Also we can now use templates only for testing to ensure that frontend rendering works as expected, without worrying about sites using the templates. + The templates were copied and extended for editorial images. + Deprecation ----------- diff --git a/Resources/Private/Language/locallang_tca.xlf b/Resources/Private/Language/locallang_tca.xlf index 51052fe..8a9bca3 100644 --- a/Resources/Private/Language/locallang_tca.xlf +++ b/Resources/Private/Language/locallang_tca.xlf @@ -84,6 +84,9 @@ Relations + + Editorial Additions + Title @@ -171,6 +174,9 @@ Unkown + + Images + Parking Facility diff --git a/Tests/Functional/Fixtures/Frontend/Rendering.typoscript b/Tests/Functional/Fixtures/Frontend/Rendering.typoscript index 9280c5b..7ab0186 100644 --- a/Tests/Functional/Fixtures/Frontend/Rendering.typoscript +++ b/Tests/Functional/Fixtures/Frontend/Rendering.typoscript @@ -1,2 +1,11 @@ page = PAGE page.10 < styles.content.get + +lib.thuecatContentElement { + partialRootPaths { + 0 = EXT:thuecat/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/ + } + templateRootPaths { + 0 = EXT:thuecat/Tests/Functional/Fixtures/Frontend/Resources/Private/Templates/ContentElement/ + } +} diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Accessibility.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Accessibility.html new file mode 100644 index 0000000..f7f294f --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Accessibility.html @@ -0,0 +1,136 @@ + +
+
+
+ +
    + + +
  • {f:translate(id: 'content.accessibilitySpecification.searchCriteria.criteria.{criteria}', default: criteria, extensionName: 'Thuecat')}
  • +
    +
    +
+
+
+
+
    + +
  • {f:translate(id: 'content.accessibilitySpecification.certification.{specification.certificationDeaf}', default: specification.certificationDeaf, extensionName: 'Thuecat')} {f:translate(id: 'content.accessibilitySpecification.certification.deaf', extensionName: 'Thuecat')}
  • +
    + +
  • {f:translate(id: 'content.accessibilitySpecification.certification.{specification.certificationMental}', default: specification.certificationMental, extensionName: 'Thuecat')} {f:translate(id: 'content.accessibilitySpecification.certification.mental', extensionName: 'Thuecat')}
  • +
    + +
  • {f:translate(id: 'content.accessibilitySpecification.certification.{specification.certificationPartiallyDeaf}', default: specification.certificationPartiallyDeaf, extensionName: 'Thuecat')} {f:translate(id: 'content.accessibilitySpecification.certification.partiallyDeaf', extensionName: 'Thuecat')}
  • +
    + +
  • {f:translate(id: 'content.accessibilitySpecification.certification.{specification.certificationPartiallyVisual}', default: specification.certificationPartiallyVisual, extensionName: 'Thuecat')} {f:translate(id: 'content.accessibilitySpecification.certification.partiallyVisual', extensionName: 'Thuecat')}
  • +
    + +
  • {f:translate(id: 'content.accessibilitySpecification.certification.{specification.certificationVisual}', default: specification.certificationVisual, extensionName: 'Thuecat')} {f:translate(id: 'content.accessibilitySpecification.certification.visual', extensionName: 'Thuecat')}
  • +
    + +
  • {f:translate(id: 'content.accessibilitySpecification.certification.{specification.certificationWalking}', default: specification.certificationWalking, extensionName: 'Thuecat')} {f:translate(id: 'content.accessibilitySpecification.certification.walking', extensionName: 'Thuecat')}
  • +
    + +
  • {f:translate(id: 'content.accessibilitySpecification.certification.{specification.certificationWheelchair}', default: specification.certificationWheelchair, extensionName: 'Thuecat')} {f:translate(id: 'content.accessibilitySpecification.certification.wheelchair', extensionName: 'Thuecat')}
  • +
    +
+
+
+ +
+ + +
+

+ +

+
+
+

{specification.shortDescriptionAllGenerations -> f:format.nl2br()}

+
+
+
+
+ + +
+

+ +

+
+
+

{specification.shortDescriptionAllergic -> f:format.nl2br()}

+
+
+
+
+ + +
+

+ +

+
+
+

{specification.shortDescriptionDeaf -> f:format.nl2br()}

+
+
+
+
+ + +
+

+ +

+
+
+

{specification.shortDescriptionMental -> f:format.nl2br()}

+
+
+
+
+ + +
+

+ +

+
+
+

{specification.shortDescriptionVisual -> f:format.nl2br()}

+
+
+
+
+ + +
+

+ +

+
+
+

{specification.shortDescriptionWalking -> f:format.nl2br()}

+
+
+
+
+
+ +
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Address.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Address.html new file mode 100644 index 0000000..2c726b8 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Address.html @@ -0,0 +1,24 @@ + +
+

+ + {address.street}
+
+ + {address.zip} {address.city}
+
+ + {address.email}
+
+ + {address.phone}
+
+ + {address.fax} + + + {f:translate(id: 'content.url', extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Digital.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Digital.html new file mode 100644 index 0000000..33df985 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Digital.html @@ -0,0 +1,9 @@ + +
+

+ + {f:translate(id: 'content.digitalOffer.{offer}', default: offer, extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Languages.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Languages.html new file mode 100644 index 0000000..8c47cd4 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Languages.html @@ -0,0 +1,9 @@ + +
+

+ + {f:translate(id: 'content.availableLanguage.{language}', default: language, extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Museum.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Museum.html new file mode 100644 index 0000000..61f79d0 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Museum.html @@ -0,0 +1,9 @@ + +
+

+ + {f:translate(id: 'content.museumService.{service}', default: service, extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Offers.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Offers.html new file mode 100644 index 0000000..445ba6b --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Offers.html @@ -0,0 +1,80 @@ + +
+ + + + + + + + +
+ + +
+ +
+

+ +

+
+
+ +
+

{offer.description}

+
+
+
+
+ +
+ {price.title} + {price.price -> f:format.currency(decimalSeparator: ',', thousandsSeparator: '.', decimals: 2, currencySign: price.currency)} + {f:translate(id: 'content.price.rule.{price.rules.0}', default: price.rule, extensionName: 'Thuecat')} + +

{price.description}

+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+

{offer.title} ({f:translate(id: 'content.price.type.{offer.type}', default: offer.type, extensionName: 'Thuecat')})

+
+ +
+

{offer.description}

+
+
+
+
+ +
+ {price.title} + {price.price -> f:format.currency(decimalSeparator: ',', thousandsSeparator: '.', decimals: 2, currencySign: price.currency)} + {f:translate(id: 'content.price.rule.{price.rules.0}', default: price.rule, extensionName: 'Thuecat')} + +

{price.description}

+
+
+
+
+
+
+
+
+
+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Opening.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Opening.html new file mode 100644 index 0000000..469dcdb --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Opening.html @@ -0,0 +1,27 @@ + +
+ + + +
+ + +
+ + +

+ {openingHour.from -> f:format.date(format: 'd.m.Y')} - + {openingHour.through -> f:format.date(format: 'd.m.Y')} +

+
+ +
+
{f:translate(id: 'content.openingHour.weekday.{weekday}', default: weekday, extensionName: 'Thuecat')}
+
{openingHour.opens} - {openingHour.closes}
+
+
+
+
+ +
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Parking.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Parking.html new file mode 100644 index 0000000..6a2802a --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Parking.html @@ -0,0 +1,22 @@ + +
+ + +
+ +
+ {parkingFacility.title} + {f:render(partial: 'Address', arguments: {address: parkingFacility.address})} +
+
+
+
+ + + {parkingFacility.title} + {f:render(partial: 'Address', arguments: {address: parkingFacility.address})} + + +
+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Payment.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Payment.html new file mode 100644 index 0000000..9a7589f --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Payment.html @@ -0,0 +1,9 @@ + +
+

+ + {f:translate(id: 'content.paymentAccepted.{payment}', default: payment, extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Photography.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Photography.html new file mode 100644 index 0000000..b76f987 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Photography.html @@ -0,0 +1,9 @@ + +
+

+ + {f:translate(id: 'content.photography.{photography}', default: photography, extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Sanitation.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Sanitation.html new file mode 100644 index 0000000..49167b3 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Sanitation.html @@ -0,0 +1,9 @@ + +
+

+ + {f:translate(id: 'content.sanitation.{sanitation}', default: sanitation, extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Service.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Service.html new file mode 100644 index 0000000..8ac667a --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Service.html @@ -0,0 +1,5 @@ + + + {f:translate(id: 'content.otherService.{service}', default: service, extensionName: 'Thuecat')} + + diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Traffic.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Traffic.html new file mode 100644 index 0000000..0939e88 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Partials/ContentElement/Traffic.html @@ -0,0 +1,9 @@ + +
+

+ + {f:translate(id: 'content.trafficInfrastructure.{trafficInfrastructure}', default: trafficInfrastructure, extensionName: 'Thuecat')} + +

+
+ diff --git a/Tests/Functional/Fixtures/Frontend/Resources/Private/Templates/ContentElement/TouristAttraction.html b/Tests/Functional/Fixtures/Frontend/Resources/Private/Templates/ContentElement/TouristAttraction.html new file mode 100644 index 0000000..3e89356 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/Resources/Private/Templates/ContentElement/TouristAttraction.html @@ -0,0 +1,170 @@ + +
+ +
+
+ {entity.slogan} +

{entity.title} ({entity.town.title})

+ {entity.description -> f:format.html()} +
+
+ + + +

ⓒ {entity.media.mainImage.author}

+
+
+ + + +
+
+
+ + +
+ + Address +

{f:translate(id: 'content.address', extensionName: 'Thuecat')}

+ {f:render(partial: 'Address', arguments: {address: entity.address, url: entity.url})} +
+ +
+ Address +

{f:translate(id: 'content.distanceToPublicTransport', extensionName: 'Thuecat')}

+

+ {entity.distanceToPublicTransport.value} {f:translate(id: 'content.unit.{entity.distanceToPublicTransport.unit}', default: entity.distanceToPublicTransport.unit, extensionName: 'Thuecat')} +

+
+
+
+
+ + Address +

{f:translate(id: 'content.parkingFacilitiesNearBy', extensionName: 'Thuecat')}

+ {f:render(partial: 'Parking', arguments: {parkingFacilitiesNearBy: entity.parkingFacilitiesNearBy})} +
+
+
+ +
+ + Address +

{f:translate(id: 'content.address', extensionName: 'Thuecat')}

+ {f:render(partial: 'Address', arguments: {address: entity.address, url: entity.url})} +
+
+
+ + Address +

{f:translate(id: 'content.distanceToPublicTransport', extensionName: 'Thuecat')}

+

+ {entity.distanceToPublicTransport.value} {f:translate(id: 'content.unit.{entity.distanceToPublicTransport.unit}', default: entity.distanceToPublicTransport.unit, extensionName: 'Thuecat')} +

+
+
+
+ + Address +

{f:translate(id: 'content.parkingFacilitiesNearBy', extensionName: 'Thuecat')}

+ {f:render(partial: 'Parking', arguments: {parkingFacilitiesNearBy: entity.parkingFacilitiesNearBy})} +
+
+
+
+
+
+ +
+

{f:translate(id: 'content.generalInformation', extensionName: 'Thuecat')}

+

+ + {f:render(partial: 'Service', arguments: {otherServices: entity.otherServices})} + + {f:translate(id: 'content.petsAllowed.{entity.petsAllowed}', default: entity.petsAllowed, extensionName: 'Thuecat')} + {f:translate(id: 'content.isAccessibleForFree.{entity.isAccessibleForFree}', default: entity.isAccessibleForFree, extensionName: 'Thuecat')} + {f:translate(id: 'content.publicAccess.{entity.publicAccess}', default: entity.publicAccess, extensionName: 'Thuecat')} + {f:translate(id: 'content.accessibilitySpecification.certificationStatus.{entity.accessibilitySpecification.certificationStatus}', default: entity.accessibilitySpecification.certificationStatus, extensionName: 'Thuecat')} +

+ +

{f:translate(id: 'content.museum', extensionName: 'Thuecat')}

+ {f:render(partial: 'Museum', arguments: {museumServices: entity.museumServices})} +
+ +

{f:translate(id: 'content.digital', extensionName: 'Thuecat')}

+ {f:render(partial: 'Digital', arguments: {digitalOffer: entity.digitalOffer})} +
+ +

{f:translate(id: 'content.traffic', extensionName: 'Thuecat')}

+ {f:render(partial: 'Traffic', arguments: {trafficInfrastructures: entity.trafficInfrastructures})} +
+ +

{f:translate(id: 'content.payment', extensionName: 'Thuecat')}

+ {f:render(partial: 'Payment', arguments: {paymentAccepted: entity.paymentAccepted})} +
+ +

{f:translate(id: 'content.languages', extensionName: 'Thuecat')}

+ {f:render(partial: 'Payment', arguments: {availableLanguages: entity.availableLanguages})} +
+ +

{f:translate(id: 'content.sanitation', extensionName: 'Thuecat')}

+ {f:render(partial: 'Sanitation', arguments: {sanitation: entity.sanitation})} +
+ +

{f:translate(id: 'content.photography', extensionName: 'Thuecat')}

+ {f:render(partial: 'Photography', arguments: {photography: entity.photography})} +
+ +

{f:translate(id: 'content.construction', extensionName: 'Thuecat')}

+

+ {entity.startOfConstruction} +

+
+ +

{f:translate(id: 'content.architecture', extensionName: 'Thuecat')}

+

+ + {f:translate(id: 'content.architecturalStyle.{style}', default: style, extensionName: 'Thuecat')} + +

+
+
+
+ +
+

{f:translate(id: 'content.openingHours', extensionName: 'Thuecat')}

+ {f:render(partial: 'Opening', arguments: {openingHours: entity.openingHours})} +
+
+ +
+

{f:translate(id: 'content.specialOpeningHours', extensionName: 'Thuecat')}

+ {f:render(partial: 'Opening', arguments: {openingHours: entity.specialOpeningHours})} +
+
+
+ +
+ +
+

{f:translate(id: 'content.offers', extensionName: 'Thuecat')}

+ {f:render(partial: 'Offers', arguments: {offers: entity.offers, uid: entity.uid})} +
+
+ +
+

{f:translate(id: 'content.accessibility', extensionName: 'Thuecat')}

+ {f:render(partial: 'Accessibility', arguments: {specification: entity.accessibilitySpecification, uid: entity.uid})} +
+
+
+
+
+ diff --git a/Tests/Functional/Fixtures/Frontend/TouristAttractionWithEditorialImages.xml b/Tests/Functional/Fixtures/Frontend/TouristAttractionWithEditorialImages.xml new file mode 100644 index 0000000..d7de1d0 --- /dev/null +++ b/Tests/Functional/Fixtures/Frontend/TouristAttractionWithEditorialImages.xml @@ -0,0 +1,58 @@ + + + + 1 + 3 + Attraktion mit redaktionellen Bildern + 2 + + + + 1 + 1 + 1 + tx_thuecat_tourist_attraction + editorial_images + 1 + sys_file + + + + 2 + 2 + 1 + tx_thuecat_tourist_attraction + editorial_images + 2 + sys_file + + + + 1 + 2 + 1 + /tourismus/images/inhalte/sehenswertes/parks_gaerten/hirschgarten/2998_Spielplaetze_Hirschgarten.jpg + jpg + image/jpeg + 2998_Spielplaetze_Hirschgarten.jpg + 61079cbeb5d13c21d20dbbcc2e28e9c8fa04b3b4 + 7329219 + 69066cc9c3b5ff135a7daa36059b18c75b3d9a23 + 4dd66a1c0a2a0ab89a22bfe734df75d9750d28f2 + + + + 2 + 2 + 1 + /tourismus/images/inhalte/sehenswertes/sehenswuerdigkeiten/Petersberg/20_Erfurt-Schriftzug_Petersberg_2021__c_Stadtverwaltung_Erfurt_CC-BY-NC-SA.JPG + JPG + image/jpeg + 20_Erfurt-Schriftzug_Petersberg_2021__c_Stadtverwaltung_Erfurt_CC-BY-NC-SA.JPG + f4c45d3c738d29162759ecd7d2dbc9af2a8f515f + 2807135 + 384f006a1452e901badb0db799fa7ff364e88a5e + 01086eae3464ef516edc0756ba3e12e35e09c33d + + + diff --git a/Tests/Functional/FrontendTest.php b/Tests/Functional/FrontendTest.php index 7650e01..4d204f4 100644 --- a/Tests/Functional/FrontendTest.php +++ b/Tests/Functional/FrontendTest.php @@ -28,27 +28,27 @@ use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; class FrontendTest extends FunctionalTestCase { - protected $coreExtensionsToLoad = [ - 'fluid_styled_content', - ]; - - protected $testExtensionsToLoad = [ - 'typo3conf/ext/thuecat/', - ]; - - protected $pathsToLinkInTestInstance = [ - 'typo3conf/ext/thuecat/Tests/Functional/Fixtures/Frontend/Sites/' => 'typo3conf/sites', - ]; - protected function setUp(): void { + $this->coreExtensionsToLoad = [ + 'fluid_styled_content', + ]; + + $this->testExtensionsToLoad = [ + 'typo3conf/ext/thuecat/', + ]; + + $this->pathsToLinkInTestInstance = [ + 'typo3conf/ext/thuecat/Tests/Functional/Fixtures/Frontend/Sites/' => 'typo3conf/sites', + ]; + parent::setUp(); $this->importDataSet('EXT:thuecat/Tests/Functional/Fixtures/Frontend/Content.xml'); $this->setUpFrontendRootPage(1, [ - 'EXT:thuecat/Tests/Functional/Fixtures/Frontend/Rendering.typoscript', 'EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript', 'EXT:thuecat/Configuration/TypoScript/ContentElements/setup.typoscript', + 'EXT:thuecat/Tests/Functional/Fixtures/Frontend/Rendering.typoscript', ]); } @@ -690,4 +690,26 @@ class FrontendTest extends FunctionalTestCase self::assertLessThan($positionSecondHour, $positionFirstHour, 'Second hour does not come after first hour.'); } + + /** + * @test + */ + public function editorialImagesOfTouristAttractionAreRenderedForDefaultLanguage(): void + { + $this->importDataSet('EXT:thuecat/Tests/Functional/Fixtures/Frontend/TouristAttractionWithEditorialImages.xml'); + + $request = new InternalRequest(); + $request = $request->withPageId(2); + + $html = (string)$this->executeFrontendRequest($request)->getBody(); + + self::assertStringContainsString( + '', + $html + ); + self::assertStringContainsString( + '', + $html, + ); + } } diff --git a/Tests/Unit/Domain/Model/Frontend/MediaTest.php b/Tests/Unit/Domain/Model/Frontend/MediaTest.php index 2e8958c..f41f15d 100644 --- a/Tests/Unit/Domain/Model/Frontend/MediaTest.php +++ b/Tests/Unit/Domain/Model/Frontend/MediaTest.php @@ -24,6 +24,7 @@ namespace WerkraumMedia\ThueCat\Tests\Unit\Domain\Model\Frontend; */ use PHPUnit\Framework\TestCase; +use TYPO3\CMS\Core\Resource\FileReference; use WerkraumMedia\ThueCat\Domain\Model\Frontend\Media; /** @@ -229,4 +230,98 @@ class MediaTest extends TestCase $subject->getExtraImages()[1]['copyrightAuthor'] ); } + + /** + * @test + */ + public function returnsEmptyArrayAsDefaultForEditorialImages(): void + { + $subject = new Media(''); + self::assertSame( + [], + $subject->getEditorialImages() + ); + } + + /** + * @test + */ + public function returnsSetEditorialImages(): void + { + $subject = new Media(''); + $reference1 = $this->createStub(FileReference::class); + $reference2 = $this->createStub(FileReference::class); + $subject->setEditorialImages([ + $reference1, + $reference2, + ]); + + $images = $subject->getEditorialImages(); + + self::assertCount(2, $images); + self::assertSame($reference1, $images[0]); + self::assertSame($reference2, $images[1]); + } + + /** + * @test + */ + public function returnsEmptyArrayAsDefaultForAllImages(): void + { + $subject = new Media(''); + self::assertSame( + [], + $subject->getAllImages() + ); + } + + /** + * @test + */ + public function returnsAllImages(): void + { + $subject = new Media(json_encode([ + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom-und-Severikirche.jpg', + 'author' => 'Full Name 1', + 'license' => [ + 'author' => 'Full Name 1 license', + ], + ], + [ + 'mainImage' => false, + 'type' => 'image', + 'title' => 'Erfurt-Dom und Severikirche-beleuchtet.jpg', + 'author' => 'Full Name 2', + 'license' => [ + 'author' => 'Full Name 2 license', + ], + ], + ]) ?: ''); + $reference1 = $this->createStub(FileReference::class); + $reference2 = $this->createStub(FileReference::class); + $subject->setEditorialImages([ + $reference1, + $reference2, + ]); + + self::assertSame( + $reference1, + $subject->getAllImages()[0] + ); + self::assertSame( + $reference2, + $subject->getAllImages()[1] + ); + self::assertSame( + 'Full Name 1', + $subject->getAllImages()[2]['copyrightAuthor'] + ); + self::assertSame( + 'Full Name 2', + $subject->getAllImages()[3]['copyrightAuthor'] + ); + } } diff --git a/ext_tables.sql b/ext_tables.sql index 32c1f79..bee11a6 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -78,6 +78,7 @@ CREATE TABLE tx_thuecat_tourist_attraction ( available_languages text, distance_to_public_transport text, accessibility_specification text, + editorial_images int(11) unsigned DEFAULT '0' NOT NULL, ); CREATE TABLE tx_thuecat_parking_facility (