events/Classes/Service/DestinationDataImportService.php
Daniel Siepmann ebb668221e Support canceled attribute during import of destination data
Destination data provides an attribute "DETAILS_ABGESAGT" to mark an
event as canceled.
We now fetch that info and mark all dates of that event as canceled.

This also contains refactoring of creation of dates within import.
All dates had the same info, and are now created at a single place.

Relates: #9280
2021-09-07 09:29:38 +02:00

825 lines
29 KiB
PHP

<?php
namespace Wrm\Events\Service;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\DataHandling\SlugHelper;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Resource\Index\MetaDataRepository;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Domain\Model\Category;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use Wrm\Events\Domain\Model\Date;
use Wrm\Events\Domain\Model\Event;
use Wrm\Events\Domain\Model\Organizer;
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\Domain\Repository\RegionRepository;
class DestinationDataImportService
{
/**
* @var
*/
protected $restUrl;
/**
* @var
*/
protected $restLicenseKey;
/**
* @var
*/
protected $restType;
/**
* @var
*/
protected $restLimit;
/**
* @var
*/
protected $restMode;
/**
* @var
*/
protected $restTemplate;
/**
* @var
*/
protected $restExperience;
/**
* @var
*/
protected $storagePid;
/**
* @var
*/
protected $categoriesPid;
/**
* @var
*/
protected $regionUid;
/**
* @var
*/
protected $categoryParentUid;
/**
* @var
*/
protected $filesFolder;
/**
* @var
*/
protected $storage;
/**
* @var array
*/
protected $settings = [];
/*
* @var
*/
protected $environment;
/**
* @var
*/
protected $tmpCurrentEvent;
/**
* @var
*/
protected $logger;
/**
* @var EventRepository
*/
protected $eventRepository;
/**
* @var RegionRepository
*/
protected $regionRepository;
/**
* @var OrganizerRepository
*/
protected $organizerRepository;
/**
* @var DateRepository
*/
protected $dateRepository;
/**
* @var CategoryRepository
*/
protected $sysCategoriesRepository;
/**
* @var FileRepository
*/
protected $fileRepository;
/**
* @var MetaDataRepository
*/
protected $metaDataRepository;
/**
* @var ConfigurationManager
*/
protected $configurationManager;
/**
* @var ObjectManager
*/
protected $objectManager;
/**
* @var PersistenceManager
*/
protected $persistenceManager;
/**
* @var ResourceFactory
*/
protected $resourceFactory;
/**
* ImportService constructor.
* @param EventRepository $eventRepository
* @param RegionRepository $regionRepository
* @param OrganizerRepository $organizerRepository
* @param DateRepository $dateRepository
* @param CategoryRepository $sysCategoriesRepository
* @param FileRepository $fileRepository
* @param MetaDataRepository $metaDataRepository
* @param ConfigurationManager $configurationManager
* @param PersistenceManager $persistenceManager
* @param ResourceFactory $resourceFactory
* @param ObjectManager $objectManager
* @param Environment $environment
*/
public function __construct(
EventRepository $eventRepository,
RegionRepository $regionRepository,
OrganizerRepository $organizerRepository,
DateRepository $dateRepository,
CategoryRepository $sysCategoriesRepository,
FileRepository $fileRepository,
MetaDataRepository $metaDataRepository,
ConfigurationManager $configurationManager,
PersistenceManager $persistenceManager,
ResourceFactory $resourceFactory,
ObjectManager $objectManager,
Environment $environment
) {
$this->eventRepository = $eventRepository;
$this->regionRepository = $regionRepository;
$this->organizerRepository = $organizerRepository;
$this->dateRepository = $dateRepository;
$this->sysCategoriesRepository = $sysCategoriesRepository;
$this->fileRepository = $fileRepository;
$this->metaDataRepository = $metaDataRepository;
$this->configurationManager = $configurationManager;
$this->persistenceManager = $persistenceManager;
$this->resourceFactory = $resourceFactory;
$this->objectManager = $objectManager;
$this->environment = $environment;
// Get Typoscript Settings
$this->settings = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS,
'Events',
'Pi1'
);
// Set properties
$this->restUrl = $this->settings['destinationData']['restUrl'];
$this->restLicenseKey = $this->settings['destinationData']['license'];
$this->restType = $this->settings['destinationData']['restType'];
$this->restLimit = $this->settings['destinationData']['restLimit'];
$this->restMode = $this->settings['destinationData']['restMode'];
$this->restTemplate = $this->settings['destinationData']['restTemplate'];
$this->sysCategoriesPid = $this->settings['destinationData']['categoriesPid'];
$this->categoryParentUid = $this->settings['destinationData']['categoryParentUid'];
}
/**
* @param $restExperience
* @param $storagePid
* @param $regionUid
* @param $filesFolder
*/
public function import($restExperience, $storagePid, $regionUid, $filesFolder)
{
$this->restExperience = $restExperience;
$this->storagePid = $storagePid;
$this->regionUid = $regionUid;
$this->filesFolder = $filesFolder;
// Get configuration
$frameworkConfiguration = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
);
// Set storage pid
$persistenceConfiguration = [
'persistence' => [
'storagePid' => $this->storagePid,
],
];
// Set Configuration
$this->configurationManager->setConfiguration(array_merge($frameworkConfiguration, $persistenceConfiguration));
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
$this->logger->info('Starting Destination Data Import Service');
$restUrl = $this->restUrl . '?experience=' . $this->restExperience . '&licensekey=' . $this->restLicenseKey . '&type=' . $this->restType . '&mode=' . $this->restMode . '&limit=' . $this->restLimit . '&template=' . $this->restTemplate;
$this->logger->info('Try to get data from ' . $restUrl);
if ($jsonResponse = json_decode(file_get_contents($restUrl), true)) {
$this->logger->info('Received data with ' . count($jsonResponse['items']) . ' items');
return $this->processData($jsonResponse);
} else {
$this->logger->error('Could not receive data.');
return 1;
}
}
/**
* @param $data
* @return int
*/
public function processData($data)
{
$this->logger->info('Processing json ' . count($data['items']));
// Get selected region
$selectedRegion = $this->regionRepository->findByUid($this->regionUid);
foreach ($data['items'] as $event) {
$this->logger->info('Processing event ' . substr($event['title'], 0, 20));
// Event already exists? If not create one!
$this->tmpCurrentEvent = $this->getOrCreateEvent($event['global_id'], $event['title']);
// Set language UID
$this->tmpCurrentEvent->setLanguageUid(-1);
// Set selected Region
$this->tmpCurrentEvent->setRegion($selectedRegion);
// Set Title
$this->tmpCurrentEvent->setTitle(substr($event['title'], 0, 254));
// Set Highlight (Is only set in rest if true)
if ($event['highlight']) {
$this->tmpCurrentEvent->setHighlight($event['highlight']);
}
// Set Texts
if ($event['texts']) {
$this->setTexts($event['texts']);
}
// Set address and geo data
if ($event['name'] || $event['street'] || $event['city'] || $event['zip'] || $event['country'] || $event['web']) {
$this->setAddress($event);
}
// Set LatLng
if ($event['geo']['main']['latitude'] && $event['geo']['main']['longitude']) {
$this->setLatLng($event['geo']['main']['latitude'], $event['geo']['main']['longitude']);
}
// Set Categories
if ($event['categories']) {
$this->setCategories($event['categories']);
}
// Set Organizer
if ($event['addresses']) {
$this->setOrganizer($event['addresses']);
}
// Set Social
if ($event['media_objects']) {
$this->setSocial($event['media_objects']);
}
// Set Tickets
if ($event['media_objects']) {
$this->setTickets($event['media_objects']);
}
// Set Dates
if ($event['timeIntervals']) {
$this->setDates(
$event['timeIntervals'],
(bool) $this->getAttributeValue($event, 'DETAILS_ABGESAGT')
);
}
// Set Assets
if ($event['media_objects']) {
$this->setAssets($event['media_objects']);
}
// Update and persist
$this->logger->info('Persist database');
$this->eventRepository->update($this->tmpCurrentEvent);
$this->persistenceManager->persistAll();
}
$this->doSlugUpdate();
$this->logger->info('Finished import');
return 0;
}
/**
*
* @param array $categories
*/
protected function setCategories(array $categories)
{
$sysParentCategory = $this->sysCategoriesRepository->findByUid($this->categoryParentUid);
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->sysCategoriesPid);
$this->sysCategoriesRepository->add($tmpSysCategory);
$this->tmpCurrentEvent->addCategory($tmpSysCategory);
} else {
$this->tmpCurrentEvent->addCategory($tmpSysCategory);
}
}
}
/**
* @param array $timeIntervals
* @TODO: split into functions
*/
protected function setDates(
array $timeIntervals,
bool $canceled
) {
// @TODO: does not seem to work -->
//$currentEventDates = $this->tmpCurrentEvent->getDates();
//$this->tmpCurrentEvent->removeAllDates($currentEventDates);
// <--
// TODO: Workaround delete dates
$currentEventDates = $this->tmpCurrentEvent->getDates();
$this->logger->info('Found ' . count($currentEventDates) . ' to delete');
foreach ($currentEventDates as $currentDate) {
$this->dateRepository->remove($currentDate);
}
$today = new \DateTime('today');
$today = $today->getTimestamp();
foreach ($timeIntervals as $date) {
// Check if dates are given as interval or not
if (empty($date['interval'])) {
if (strtotime($date['start']) > $today) {
$this->logger->info('Setup single date');
$start = new \DateTime($date['start'], new \DateTimeZone($date['tz']));
$end = new \DateTime($date['end'], new \DateTimeZone($date['tz']));
$this->logger->info('Start transformed ' . $start->format('Y-m-d H:i'));
$this->logger->info('End transformed ' . $end->format('Y-m-d H:i'));
$this->tmpCurrentEvent->addDate(Date::createFromDestinationData(
$start,
$end,
$canceled
));
}
} else {
if ($date['freq'] == 'Daily' && empty($date['weekdays']) && !empty($date['repeatUntil'])) {
$this->logger->info('Setup daily interval dates');
$this->logger->info('Start ' . $date['start']);
$this->logger->info('End ' . $date['repeatUntil']);
$start = new \DateTime($date['start'], new \DateTimeZone($date['tz']));
$until = new \DateTime($date['repeatUntil'], new \DateTimeZone($date['tz']));
for ($i = strtotime($start->format('l'), $start->getTimestamp()); $i <= $until->getTimestamp(); $i = strtotime('+1 day', $i)) {
if ($i >= $today) {
$eventStart = new \DateTime();
$eventStart->setTimestamp($i);
$eventStart->setTime($start->format('H'), $start->format('i'));
$eventEnd = new \DateTime();
$eventEnd->setTimestamp($i);
$eventEnd->setTime($until->format('H'), $until->format('i'));
$this->tmpCurrentEvent->addDate(Date::createFromDestinationData(
$eventStart,
$eventEnd,
$canceled
));
}
}
} elseif ($date['freq'] == 'Weekly' && !empty($date['weekdays']) && !empty($date['repeatUntil'])) {
foreach ($date['weekdays'] as $day) {
$this->logger->info('Setup weekly interval dates for ' . $day);
$this->logger->info('Start ' . $date['start']);
$this->logger->info('End ' . $date['repeatUntil']);
$start = new \DateTime($date['start'], new \DateTimeZone($date['tz']));
$until = new \DateTime($date['repeatUntil'], new \DateTimeZone($date['tz']));
for ($i = strtotime($day, $start->getTimestamp()); $i <= $until->getTimestamp(); $i = strtotime('+1 week', $i)) {
if ($i >= $today) {
$eventStart = new \DateTime();
$eventStart->setTimestamp($i);
$eventStart->setTime($start->format('H'), $start->format('i'));
$eventEnd = new \DateTime();
$eventEnd->setTimestamp($i);
$eventEnd->setTime($until->format('H'), $until->format('i'));
$this->tmpCurrentEvent->addDate(Date::createFromDestinationData(
$eventStart,
$eventEnd,
$canceled
));
}
}
}
}
}
}
$this->logger->info('Finished setup dates');
}
/**
* @param array $addresses
*/
protected function setOrganizer(array $addresses)
{
foreach ($addresses as $address) {
if ($address['rel'] == "organizer") {
$tmpOrganizer = $this->organizerRepository->findOneByName($address['name']);
if ($tmpOrganizer) {
$this->tmpCurrentEvent->setOrganizer($tmpOrganizer);
continue;
}
$tmpOrganizer = $this->objectManager->get(Organizer::class);
$tmpOrganizer->setLanguageUid(-1);
$tmpOrganizer->setName($address['name']);
$tmpOrganizer->setCity($address['city']);
$tmpOrganizer->setZip($address['zip']);
$tmpOrganizer->setStreet($address['street']);
$tmpOrganizer->setPhone($address['phone']);
$tmpOrganizer->setWeb($address['web']);
$tmpOrganizer->setEmail($address['email']);
$tmpOrganizer->setDistrict($address['district']);
$this->organizerRepository->add($tmpOrganizer);
$this->tmpCurrentEvent->setOrganizer($tmpOrganizer);
}
}
}
/**
* @param array $event
*/
protected function setAddress(array $event)
{
if (!empty($event['name'])) {
$this->tmpCurrentEvent->setName($event['name']);
}
if (!empty($event['street'])) {
$this->tmpCurrentEvent->setStreet($event['street']);
}
if (!empty($event['city'])) {
$this->tmpCurrentEvent->setCity($event['city']);
}
if (!empty($event['zip'])) {
$this->tmpCurrentEvent->setZip($event['zip']);
}
if (!empty($event['country'])) {
$this->tmpCurrentEvent->setCountry($event['country']);
}
if (!empty($event['phone'])) {
$this->tmpCurrentEvent->setPhone($event['phone']);
}
if (!empty($event['web'])) {
$this->tmpCurrentEvent->setWeb($event['web']);
}
}
/**
* @param array $media
*/
protected function setSocial(array $media)
{
foreach ($media as $link) {
if ($link['rel'] == "socialmedia" && $link['value'] == "Facebook") {
$this->tmpCurrentEvent->setFacebook($link['url']);
}
if ($link['rel'] == "socialmedia" && $link['value'] == "YouTube") {
$this->tmpCurrentEvent->setYouTube($link['url']);
}
if ($link['rel'] == "socialmedia" && $link['value'] == "Instagram") {
$this->tmpCurrentEvent->setInstagram($link['url']);
}
}
}
/**
* @param array $media
*/
protected function setTickets(array $media)
{
foreach ($media as $link) {
if ($link['rel'] == "ticket") {
$this->tmpCurrentEvent->setTicket($link['url']);
break;
} elseif ($link['rel'] == "booking" && !$this->multi_array_key_exists('ticket', $media)) {
$this->tmpCurrentEvent->setTicket($link['url']);
break;
} elseif ($link['rel'] == "PRICE_KARTENLINK" && !$this->multi_array_key_exists('ticket', $media) && !$this->multi_array_key_exists('booking', $media)) {
$this->tmpCurrentEvent->setTicket($link['url']);
}
}
}
/**
* @param string $needle
* @param array $haystack
*/
protected function multi_array_key_exists($needle, $haystack)
{
foreach ($haystack as $key => $value) {
if ($needle == $key) {
return true;
}
if (is_array($value)) {
if ($this->multi_array_key_exists($needle, $value) == true) {
return true;
}
}
}
return false;
}
/**
* @param string $lat
* @param string $lng
*/
protected function setLatLng(string $lat, string $lng)
{
$this->tmpCurrentEvent->setLatitude($lat);
$this->tmpCurrentEvent->setLongitude($lng);
}
/**
* Set Texts
* @param Array $texts
*/
protected function setTexts(array $texts)
{
foreach ($texts as $text) {
if ($text['rel'] == "details" && $text['type'] == "text/plain") {
$this->tmpCurrentEvent->setDetails(str_replace('\n\n', '\n', $text['value']));
}
if ($text['rel'] == "teaser" && $text['type'] == "text/plain") {
$this->tmpCurrentEvent->setTeaser(str_replace('\n\n', '\n', $text['value']));
}
if ($text['rel'] == "PRICE_INFO" && $text['type'] == "text/plain") {
$this->tmpCurrentEvent->setPriceInfo(str_replace('\n\n', '\n', $text['value']));
}
}
}
/**
* Load File
* @param String $globalId
* @param String $title
*/
protected function getOrCreateEvent(string $globalId, string $title)
{
$event = $this->eventRepository->findOneByGlobalId($globalId);
if ($event) {
// Global ID is found and events gets updated
$event = $this->eventRepository->findOneByGlobalId($globalId);
$this->logger->info('Found "' . substr($title, 0, 20) . '..." with global id ' . $globalId . ' in database');
return $event;
}
// New event is created
$this->logger->info(substr($title, 0, 20) . ' does not exist');
$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('Not found "' . substr($title, 0, 20) . '..." with global id ' . $globalId . ' in database. Created new one.');
return $event;
}
/**
* @param array $assets
*/
protected function setAssets(array $assets)
{
$this->logger->info("Set assets");
$error = false;
foreach ($assets as $media_object) {
if ($media_object['rel'] == "default" && $media_object['type'] == "image/jpeg") {
$this->storage = $this->resourceFactory->getDefaultStorage();
$orgFileUrl = urldecode($media_object['url']);
$orgFileNameSanitized = $this->storage->sanitizeFileName(
basename(
urldecode($media_object['url'])
)
);
$this->logger->info('File attached:' . $orgFileUrl);
$this->logger->info('File attached sanitized:' . $orgFileNameSanitized);
//die();
if ($this->storage == null) {
$this->logger->error('No default storage defined. Cancel import.');
die();
}
// Check if file already exists
if (file_exists($this->environment->getPublicPath() . '/fileadmin/' . $this->filesFolder . $orgFileNameSanitized)) {
$this->logger->info('File already exists');
} else {
$this->logger->info("File don't exist " . $orgFileNameSanitized);
// Load the file
if ($file = $this->loadFile($orgFileUrl)) {
// Move file to defined folder
$this->logger->info('Adding file ' . $file);
try {
$targetFolder = $this->storage->getFolder($this->filesFolder);
} catch (FolderDoesNotExistException $e) {
$targetFolder = $this->storage->createFolder($this->filesFolder);
}
$this->storage->addFile($this->environment->getPublicPath() . "/uploads/tx_events/" . $file, $targetFolder);
} else {
$error = true;
}
}
if ($error !== true) {
if ($this->tmpCurrentEvent->getImages()->count() > 0) {
$this->logger->info('Relation found');
// TODO: How to delete file references?
} else {
$this->logger->info('No relation found');
$file = $this->storage->getFile($this->filesFolder . $orgFileNameSanitized);
$this->metaDataRepository->update($file->getUid(), array('title' => $media_object['value'], 'description' => $media_object['description'], 'alternative' => 'DD Import'));
$this->createFileRelations($file->getUid(), 'tx_events_domain_model_event', $this->tmpCurrentEvent->getUid(), 'images', $this->storagePid);
}
}
}
$error = false;
}
}
/**
* Load File
* @param string $file
* @return string
*/
protected function loadFile($file)
{
$directory = $this->environment->getPublicPath() . "/uploads/tx_events/";
$filename = basename($file);
$this->logger->info('Getting file ' . $file . ' as ' . $filename);
$asset = GeneralUtility::getUrl($file);
if ($asset) {
file_put_contents($directory . $filename, $asset);
return $filename;
}
$this->logger->error('Cannot load file ' . $file);
return false;
}
/**
* Build relations for FAL
* @param int $uid_local
* @param string $tablenames
* @param int $uid_foreign
* @param string $fieldname
* @param string $storagePid
* @return bool
*/
protected function createFileRelations($uid_local, $tablenames, $uid_foreign, $fieldname, $storagePid)
{
$newId = 'NEW1234';
$data = array();
$data['sys_file_reference'][$newId] = array(
'table_local' => 'sys_file',
'uid_local' => $uid_local,
'tablenames' => $tablenames,
'uid_foreign' => $uid_foreign,
'fieldname' => $fieldname,
'pid' => $storagePid
);
$data[$tablenames][$uid_foreign] = array(
'pid' => $storagePid,
$fieldname => $newId
);
$dataHandler = $this->objectManager->get(DataHandler::class);
$dataHandler->start($data, array());
$dataHandler->process_datamap();
if (count($dataHandler->errorLog) === 0) {
return true;
}
foreach ($dataHandler->errorLog as $error) {
$this->logger->info($error);
}
return false;
}
/**
* Performs slug update
* @return bool
*/
protected function doSlugUpdate()
{
$this->logger->info('Update slugs');
$slugHelper = GeneralUtility::makeInstance(
SlugHelper::class,
'tx_events_domain_model_event',
'slug',
$GLOBALS['TCA']['tx_events_domain_model_event']['columns']['slug']['config']
);
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_events_domain_model_event');
$queryBuilder = $connection->createQueryBuilder();
$queryBuilder->getRestrictions()->removeAll();
$statement = $queryBuilder->select('uid', 'global_id')
->from('tx_events_domain_model_event')
->where(
$queryBuilder->expr()->orX(
$queryBuilder->expr()->eq('slug', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)),
$queryBuilder->expr()->isNull('slug')
)
)
->execute();
while ($record = $statement->fetch()) {
$queryBuilder = $connection->createQueryBuilder();
$queryBuilder->update('tx_events_domain_model_event')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($record['uid'], \PDO::PARAM_INT)
)
)
->set('slug', $slugHelper->sanitize((string)$record['global_id']));
$queryBuilder->getSQL();
$queryBuilder->execute();
}
return true;
}
/**
* Fetch the value for requested attribute.
*
* Returns first if multiple attributes with same key exist.
* Casts "true" and "false" to true and false.
* Return null in case no attribute value could exists.
*/
private function getAttributeValue(
array $event,
string $attributeKey
) {
$attributes = array_filter($event['attributes'] ?? [], function (array $attribute) use ($attributeKey) {
$currentKey = $attribute['key'] ?? '';
return $currentKey === $attributeKey;
});
if ($attributes === []) {
return null;
}
$value = $attributes[0]['value'] ?? null;
if ($value === 'true') {
return true;
}
if ($value === 'false') {
return false;
}
return $value;
}
}