mirror of
https://github.com/werkraum-media/events.git
synced 2024-11-25 04:56:10 +01:00
Add meta tags (#49)
A new class is added which will add meta tags for dates and events. The class has an interface which allows it to be replaced via DI to alter behaviour. Refactor import regarding data handler. We now also need to add a new column "keywords". We use the new DataHandler approach. But that approach only covered relations so far. We therefore refactor that area to be more generic and use that one for new keywords column. Relates: #10642
This commit is contained in:
parent
8851a4e299
commit
713bc4b697
23 changed files with 646 additions and 64 deletions
|
@ -16,6 +16,7 @@ use WerkraumMedia\Events\Domain\Repository\DateRepository;
|
||||||
use WerkraumMedia\Events\Domain\Repository\RegionRepository;
|
use WerkraumMedia\Events\Domain\Repository\RegionRepository;
|
||||||
use WerkraumMedia\Events\Events\Controller\DateListVariables;
|
use WerkraumMedia\Events\Events\Controller\DateListVariables;
|
||||||
use WerkraumMedia\Events\Events\Controller\DateSearchVariables;
|
use WerkraumMedia\Events\Events\Controller\DateSearchVariables;
|
||||||
|
use WerkraumMedia\Events\Frontend\MetaInformation\DateMetaInformationInterface;
|
||||||
use WerkraumMedia\Events\Pagination\Factory;
|
use WerkraumMedia\Events\Pagination\Factory;
|
||||||
use WerkraumMedia\Events\Service\DataProcessingForModels;
|
use WerkraumMedia\Events\Service\DataProcessingForModels;
|
||||||
|
|
||||||
|
@ -28,7 +29,8 @@ final class DateController extends AbstractController
|
||||||
private readonly CategoryRepository $categoryRepository,
|
private readonly CategoryRepository $categoryRepository,
|
||||||
private readonly Factory $paginationFactory,
|
private readonly Factory $paginationFactory,
|
||||||
private readonly DataProcessingForModels $dataProcessing,
|
private readonly DataProcessingForModels $dataProcessing,
|
||||||
private readonly ExtensionService $extensionService
|
private readonly ExtensionService $extensionService,
|
||||||
|
private readonly DateMetaInformationInterface $metaInformationService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +112,7 @@ final class DateController extends AbstractController
|
||||||
$this->trigger404('No event found for requested date.');
|
$this->trigger404('No event found for requested date.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->metaInformationService->setDate($date);
|
||||||
$this->view->assign('date', $date);
|
$this->view->assign('date', $date);
|
||||||
return $this->htmlResponse();
|
return $this->htmlResponse();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use TYPO3\CMS\Extbase\Annotation as Extbase;
|
||||||
use WerkraumMedia\Events\Domain\Model\Dto\EventDemandFactory;
|
use WerkraumMedia\Events\Domain\Model\Dto\EventDemandFactory;
|
||||||
use WerkraumMedia\Events\Domain\Model\Event;
|
use WerkraumMedia\Events\Domain\Model\Event;
|
||||||
use WerkraumMedia\Events\Domain\Repository\EventRepository;
|
use WerkraumMedia\Events\Domain\Repository\EventRepository;
|
||||||
|
use WerkraumMedia\Events\Frontend\MetaInformation\EventMetaInformationInterface;
|
||||||
use WerkraumMedia\Events\Service\DataProcessingForModels;
|
use WerkraumMedia\Events\Service\DataProcessingForModels;
|
||||||
|
|
||||||
final class EventController extends AbstractController
|
final class EventController extends AbstractController
|
||||||
|
@ -16,7 +17,8 @@ final class EventController extends AbstractController
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly EventRepository $eventRepository,
|
private readonly EventRepository $eventRepository,
|
||||||
private readonly DataProcessingForModels $dataProcessing,
|
private readonly DataProcessingForModels $dataProcessing,
|
||||||
private readonly EventDemandFactory $demandFactory
|
private readonly EventDemandFactory $demandFactory,
|
||||||
|
private readonly EventMetaInformationInterface $metaInformationService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ final class EventController extends AbstractController
|
||||||
#[Extbase\IgnoreValidation(['value' => 'event'])]
|
#[Extbase\IgnoreValidation(['value' => 'event'])]
|
||||||
public function showAction(Event $event): ResponseInterface
|
public function showAction(Event $event): ResponseInterface
|
||||||
{
|
{
|
||||||
|
$this->metaInformationService->setEvent($event);
|
||||||
$this->view->assign('event', $event);
|
$this->view->assign('event', $event);
|
||||||
return $this->htmlResponse();
|
return $this->htmlResponse();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,13 @@ class Date extends AbstractEntity
|
||||||
|
|
||||||
protected ?Date $originalDate = null;
|
protected ?Date $originalDate = null;
|
||||||
|
|
||||||
protected Event $event;
|
/**
|
||||||
|
* Can not be null in theory.
|
||||||
|
* But editors might disable an event.
|
||||||
|
* The date might still be available by Extbase, but without event.
|
||||||
|
* This needs to be handled properly by consuming code for now.
|
||||||
|
*/
|
||||||
|
protected ?Event $event;
|
||||||
|
|
||||||
protected string $canceledLink = '';
|
protected string $canceledLink = '';
|
||||||
|
|
||||||
|
@ -65,7 +71,7 @@ class Date extends AbstractEntity
|
||||||
return $end && $this->getStart()->format('Y-m-d') === $end->format('Y-m-d');
|
return $end && $this->getStart()->format('Y-m-d') === $end->format('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEvent(): Event
|
public function getEvent(): ?Event
|
||||||
{
|
{
|
||||||
return $this->event;
|
return $this->event;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ class Event extends AbstractEntity
|
||||||
*/
|
*/
|
||||||
protected ObjectStorage $features;
|
protected ObjectStorage $features;
|
||||||
|
|
||||||
|
protected string $keywords;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ObjectStorage<Partner>
|
* @var ObjectStorage<Partner>
|
||||||
*/
|
*/
|
||||||
|
@ -370,6 +372,11 @@ class Event extends AbstractEntity
|
||||||
return $this->getSortedCategory($this->features);
|
return $this->getSortedCategory($this->features);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getKeywords(): string
|
||||||
|
{
|
||||||
|
return $this->keywords;
|
||||||
|
}
|
||||||
|
|
||||||
public function setLanguageUid(int $languageUid): void
|
public function setLanguageUid(int $languageUid): void
|
||||||
{
|
{
|
||||||
$this->_languageUid = $languageUid;
|
$this->_languageUid = $languageUid;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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\Events\Frontend\MetaInformation;
|
||||||
|
|
||||||
|
use WerkraumMedia\Events\Domain\Model\Date;
|
||||||
|
|
||||||
|
interface DateMetaInformationInterface
|
||||||
|
{
|
||||||
|
public function setDate(Date $date): void;
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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\Events\Frontend\MetaInformation;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
|
||||||
|
use WerkraumMedia\Events\Domain\Model\Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TYPO3 has many different APIs to set meta information like: Page Title, Meta Tags, OpenGraph Tags, etc.
|
||||||
|
* Those are combined here for Date detail view.
|
||||||
|
* That way there is a single place to connect the details to TYPO3 APIs.
|
||||||
|
*/
|
||||||
|
final class DateMetaInformationService implements DateMetaInformationInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly MetaTagManagerRegistry $metaTagManagerRegistry
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDate(Date $date): void
|
||||||
|
{
|
||||||
|
$this->setDescription($date);
|
||||||
|
$this->setKeywords($date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setDescription(Date $date): void
|
||||||
|
{
|
||||||
|
$description = $date->getEvent()?->getTeaser() ?? '';
|
||||||
|
if ($description === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metaTagManagerRegistry
|
||||||
|
->getManagerForProperty('description')
|
||||||
|
->addProperty('description', $description)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setKeywords(Date $date): void
|
||||||
|
{
|
||||||
|
$keywords = $date->getEvent()?->getKeywords() ?? '';
|
||||||
|
if ($keywords === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metaTagManagerRegistry
|
||||||
|
->getManagerForProperty('keywords')
|
||||||
|
->addProperty('keywords', $keywords)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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\Events\Frontend\MetaInformation;
|
||||||
|
|
||||||
|
use WerkraumMedia\Events\Domain\Model\Event;
|
||||||
|
|
||||||
|
interface EventMetaInformationInterface
|
||||||
|
{
|
||||||
|
public function setEvent(Event $event): void;
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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\Events\Frontend\MetaInformation;
|
||||||
|
|
||||||
|
use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
|
||||||
|
use WerkraumMedia\Events\Domain\Model\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TYPO3 has many different APIs to set meta information like: Page Title, Meta Tags, OpenGraph Tags, etc.
|
||||||
|
* Those are combined here for Event detail view.
|
||||||
|
* That way there is a single place to connect the details to TYPO3 APIs.
|
||||||
|
*/
|
||||||
|
final class EventMetaInformationService implements EventMetaInformationInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly MetaTagManagerRegistry $metaTagManagerRegistry
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEvent(Event $event): void
|
||||||
|
{
|
||||||
|
$this->setDescription($event);
|
||||||
|
$this->setKeywords($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setDescription(Event $event): void
|
||||||
|
{
|
||||||
|
$description = $event->getTeaser();
|
||||||
|
if ($description === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metaTagManagerRegistry
|
||||||
|
->getManagerForProperty('description')
|
||||||
|
->addProperty('description', $description)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setKeywords(Event $event): void
|
||||||
|
{
|
||||||
|
$keywords = $event->getKeywords();
|
||||||
|
if ($keywords === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->metaTagManagerRegistry
|
||||||
|
->getManagerForProperty('keywords')
|
||||||
|
->addProperty('keywords', $keywords)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
|
@ -182,13 +182,20 @@ final class DestinationDataImportService
|
||||||
$this->persistenceManager->persistAll();
|
$this->persistenceManager->persistAll();
|
||||||
|
|
||||||
// Apply changes via DataHandler (The new way)
|
// Apply changes via DataHandler (The new way)
|
||||||
|
$eventUid = $this->tmpCurrentEvent->getUid();
|
||||||
|
if (is_int($eventUid) === false) {
|
||||||
|
throw new Exception('Could not persist and fetch uid of event.', 1701244570);
|
||||||
|
}
|
||||||
|
|
||||||
$this->logger->info('Apply changes via DataHandler');
|
$this->logger->info('Apply changes via DataHandler');
|
||||||
if ($event['categories'] ?? false) {
|
$this->dataHandler->updateEvent(
|
||||||
$this->setCategories($event['categories']);
|
$eventUid,
|
||||||
}
|
[
|
||||||
if ($event['features']) {
|
new Assignment('keywords', implode(', ', $event['keywords'] ?? [])),
|
||||||
$this->setFeatures($event['features']);
|
$this->getCategories($event['categories'] ?? []),
|
||||||
}
|
$this->getFeatures($event['features'] ?? []),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
$this->logger->info('Update slugs');
|
$this->logger->info('Update slugs');
|
||||||
$this->slugger->update('tx_events_domain_model_event');
|
$this->slugger->update('tx_events_domain_model_event');
|
||||||
|
@ -202,7 +209,7 @@ final class DestinationDataImportService
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setCategories(array $categories): void
|
private function getCategories(array $categories): Assignment
|
||||||
{
|
{
|
||||||
$categories = $this->categoriesAssignment->getCategories(new CategoryImport(
|
$categories = $this->categoriesAssignment->getCategories(new CategoryImport(
|
||||||
$this->import->getCategoryParent(),
|
$this->import->getCategoryParent(),
|
||||||
|
@ -216,14 +223,13 @@ final class DestinationDataImportService
|
||||||
);
|
);
|
||||||
$this->eventDispatcher->dispatch($event);
|
$this->eventDispatcher->dispatch($event);
|
||||||
|
|
||||||
$this->dataHandler->storeAssignments(new Assignment(
|
return Assignment::createFromDomainObjects(
|
||||||
$this->tmpCurrentEvent->getUid(),
|
|
||||||
'categories',
|
'categories',
|
||||||
$event->getCategories()->toArray()
|
$event->getCategories()->toArray()
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setFeatures(array $features): void
|
private function getFeatures(array $features): Assignment
|
||||||
{
|
{
|
||||||
$features = $this->categoriesAssignment->getCategories(new CategoryImport(
|
$features = $this->categoriesAssignment->getCategories(new CategoryImport(
|
||||||
$this->import->getFeaturesParent(),
|
$this->import->getFeaturesParent(),
|
||||||
|
@ -232,11 +238,10 @@ final class DestinationDataImportService
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->dataHandler->storeAssignments(new Assignment(
|
return Assignment::createFromDomainObjects(
|
||||||
$this->tmpCurrentEvent->getUid(),
|
|
||||||
'features',
|
'features',
|
||||||
$features->toArray()
|
$features->toArray()
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setDates(
|
private function setDates(
|
||||||
|
|
|
@ -39,25 +39,26 @@ final class DataHandler
|
||||||
$this->logger = $logManager->getLogger(self::class);
|
$this->logger = $logManager->getLogger(self::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storeAssignments(
|
/**
|
||||||
Assignment $assignment
|
* @param Assignment[] $assignments
|
||||||
|
*/
|
||||||
|
public function updateEvent(
|
||||||
|
int $eventUid,
|
||||||
|
array $assignments
|
||||||
): void {
|
): void {
|
||||||
$data = [
|
$data = ['tx_events_domain_model_event' => [$eventUid => []]];
|
||||||
'tx_events_domain_model_event' => [
|
foreach ($assignments as $assignment) {
|
||||||
$assignment->getUid() => [
|
$data['tx_events_domain_model_event'][$eventUid][$assignment->getColumnName()] = $assignment->getValue();
|
||||||
$assignment->getColumnName() => implode(',', $assignment->getUids()),
|
}
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->logger->debug('Import assignment.', $data);
|
$this->logger->debug('Update event data.', $data);
|
||||||
$dataHandler = GeneralUtility::makeInstance(Typo3DataHandler::class);
|
$dataHandler = GeneralUtility::makeInstance(Typo3DataHandler::class);
|
||||||
$dataHandler->start($data, []);
|
$dataHandler->start($data, []);
|
||||||
$dataHandler->process_datamap();
|
$dataHandler->process_datamap();
|
||||||
|
|
||||||
if ($dataHandler->errorLog !== []) {
|
if ($dataHandler->errorLog !== []) {
|
||||||
$this->logger->error('Error during import of assignments.', [
|
$this->logger->error('Error during update of event data.', [
|
||||||
'assignment' => $assignment,
|
'assignments' => $assignments,
|
||||||
'errors' => $dataHandler->errorLog,
|
'errors' => $dataHandler->errorLog,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,38 +28,10 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject;
|
||||||
|
|
||||||
final class Assignment
|
final class Assignment
|
||||||
{
|
{
|
||||||
private readonly int $uid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int[]
|
|
||||||
*/
|
|
||||||
private readonly array $uids;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param AbstractDomainObject[] $assignments
|
|
||||||
*/
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
?int $uid,
|
|
||||||
private readonly string $columnName,
|
private readonly string $columnName,
|
||||||
array $assignments
|
private readonly string $value,
|
||||||
) {
|
) {
|
||||||
if (is_int($uid) === false) {
|
|
||||||
throw new InvalidArgumentException('Only integer allowed as uid, need a persisted entity.', 1699352008);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->uid = $uid;
|
|
||||||
$this->uids = array_map(static function (AbstractDomainObject $model): int {
|
|
||||||
$uid = $model->getUid();
|
|
||||||
if (is_int($uid) === false) {
|
|
||||||
throw new InvalidArgumentException('Only object with uid supported.', 1698936965);
|
|
||||||
}
|
|
||||||
return $uid;
|
|
||||||
}, $assignments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUid(): int
|
|
||||||
{
|
|
||||||
return $this->uid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getColumnName(): string
|
public function getColumnName(): string
|
||||||
|
@ -67,11 +39,29 @@ final class Assignment
|
||||||
return $this->columnName;
|
return $this->columnName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getValue(): string
|
||||||
* @return int[]
|
|
||||||
*/
|
|
||||||
public function getUids(): array
|
|
||||||
{
|
{
|
||||||
return $this->uids;
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AbstractDomainObject[] $objects
|
||||||
|
*/
|
||||||
|
public static function createFromDomainObjects(
|
||||||
|
string $columnName,
|
||||||
|
array $objects
|
||||||
|
): self {
|
||||||
|
$uids = array_map(static function (AbstractDomainObject $model): int {
|
||||||
|
$uid = $model->getUid();
|
||||||
|
if (is_int($uid) === false) {
|
||||||
|
throw new InvalidArgumentException('Only object with uid supported.', 1698936965);
|
||||||
|
}
|
||||||
|
return $uid;
|
||||||
|
}, $objects);
|
||||||
|
|
||||||
|
return new self(
|
||||||
|
$columnName,
|
||||||
|
implode(',', $uids)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ return [
|
||||||
partner,
|
partner,
|
||||||
categories,
|
categories,
|
||||||
features,
|
features,
|
||||||
|
keywords,
|
||||||
references_events,
|
references_events,
|
||||||
pages,
|
pages,
|
||||||
--div--;' . $l10nPath . ':tx_events_domain_model_event.tabs.media,
|
--div--;' . $l10nPath . ':tx_events_domain_model_event.tabs.media,
|
||||||
|
@ -357,6 +358,15 @@ return [
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'keywords' => [
|
||||||
|
'exclude' => true,
|
||||||
|
'label' => 'Keywords',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'text',
|
||||||
|
'cols' => 40,
|
||||||
|
'rows' => 3,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
'dates' => [
|
'dates' => [
|
||||||
'exclude' => true,
|
'exclude' => true,
|
||||||
|
|
|
@ -26,6 +26,13 @@ Features
|
||||||
|
|
||||||
* Support PHP 8.1, 8.2, 8.3.
|
* Support PHP 8.1, 8.2, 8.3.
|
||||||
|
|
||||||
|
* Add meta tags.
|
||||||
|
A new class is added which will add meta tags for dates and events.
|
||||||
|
The class has an interface which allows it to be replaced via DI to alter behaviour.
|
||||||
|
|
||||||
|
* Import keywords for events from destination.one.
|
||||||
|
That way keywords are available for usage in meta tags.
|
||||||
|
|
||||||
Fixes
|
Fixes
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
15
Documentation/Features/MetaTags.rst
Normal file
15
Documentation/Features/MetaTags.rst
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
.. index:: single: meta tags
|
||||||
|
.. _metaTags:
|
||||||
|
|
||||||
|
Meta Tags
|
||||||
|
=========
|
||||||
|
|
||||||
|
The extension comes with default implementations for meta tags.
|
||||||
|
|
||||||
|
The default implementation can be exchanged by leveraging TYPO3 Dependency Injection.
|
||||||
|
|
||||||
|
Further resources:
|
||||||
|
|
||||||
|
* https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ApiOverview/Seo/MetaTagApi.html
|
||||||
|
|
||||||
|
* https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/ApiOverview/DependencyInjection/Index.html
|
|
@ -187,4 +187,21 @@ class DatesTest extends AbstractFunctionalTestCase
|
||||||
self::assertStringNotContainsString('Event 1', $html);
|
self::assertStringNotContainsString('Event 1', $html);
|
||||||
self::assertStringContainsString('Event 2', $html);
|
self::assertStringContainsString('Event 2', $html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function addsMetaTags(): void
|
||||||
|
{
|
||||||
|
$this->importPHPDataSet(__DIR__ . '/DatesTestFixtures/DateMetaTags.php');
|
||||||
|
|
||||||
|
$request = new InternalRequest();
|
||||||
|
$request = $request->withPageId(1);
|
||||||
|
$request = $request->withQueryParameter('tx_events_dateshow[date]', '1');
|
||||||
|
$response = $this->executeFrontendSubRequest($request);
|
||||||
|
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
$html = (string)$response->getBody();
|
||||||
|
|
||||||
|
self::assertStringContainsString('<meta name="description" content="Teaser of Event" />', $html);
|
||||||
|
self::assertStringContainsString('<meta name="keywords" content="Gewölbe, Goethe, Horst Damm, Kästner, Theater" />', $html);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
34
Tests/Functional/Frontend/DatesTestFixtures/DateMetaTags.php
Normal file
34
Tests/Functional/Frontend/DatesTestFixtures/DateMetaTags.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tt_content' => [
|
||||||
|
0 => [
|
||||||
|
'uid' => '1',
|
||||||
|
'pid' => '1',
|
||||||
|
'CType' => 'list',
|
||||||
|
'list_type' => 'events_dateshow',
|
||||||
|
'header' => 'Singleview',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'tx_events_domain_model_event' => [
|
||||||
|
0 => [
|
||||||
|
'uid' => '1',
|
||||||
|
'pid' => '2',
|
||||||
|
'title' => 'Title of Event',
|
||||||
|
'teaser' => 'Teaser of Event',
|
||||||
|
'keywords' => 'Gewölbe, Goethe, Horst Damm, Kästner, Theater',
|
||||||
|
'hidden' => '0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'tx_events_domain_model_date' => [
|
||||||
|
0 => [
|
||||||
|
'uid' => '1',
|
||||||
|
'pid' => '2',
|
||||||
|
'event' => '1',
|
||||||
|
'start' => '1676419200',
|
||||||
|
'end' => '1676484000',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
60
Tests/Functional/Frontend/EventsTest.php
Normal file
60
Tests/Functional/Frontend/EventsTest.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||||
|
*
|
||||||
|
* 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\Events\Tests\Functional\Frontend;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
|
||||||
|
use WerkraumMedia\Events\Tests\Functional\AbstractFunctionalTestCase;
|
||||||
|
|
||||||
|
class EventsTest extends AbstractFunctionalTestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->testExtensionsToLoad = [
|
||||||
|
'typo3conf/ext/events/Tests/Functional/Frontend/Fixtures/Extensions/example',
|
||||||
|
];
|
||||||
|
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->importPHPDataSet(__DIR__ . '/Fixtures/Database/SiteStructure.php');
|
||||||
|
$this->setUpFrontendRendering();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function addsMetaTags(): void
|
||||||
|
{
|
||||||
|
$this->importPHPDataSet(__DIR__ . '/EventsTestFixtures/EventMetaTags.php');
|
||||||
|
|
||||||
|
$request = new InternalRequest();
|
||||||
|
$request = $request->withPageId(1);
|
||||||
|
$request = $request->withQueryParameter('tx_events_eventshow[event]', '1');
|
||||||
|
$response = $this->executeFrontendSubRequest($request);
|
||||||
|
|
||||||
|
self::assertSame(200, $response->getStatusCode());
|
||||||
|
$html = (string)$response->getBody();
|
||||||
|
|
||||||
|
self::assertStringContainsString('<meta name="description" content="Teaser of Event" />', $html);
|
||||||
|
self::assertStringContainsString('<meta name="keywords" content="Gewölbe, Goethe, Horst Damm, Kästner, Theater" />', $html);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tt_content' => [
|
||||||
|
0 => [
|
||||||
|
'uid' => '1',
|
||||||
|
'pid' => '1',
|
||||||
|
'CType' => 'list',
|
||||||
|
'list_type' => 'events_eventshow',
|
||||||
|
'header' => 'Singleview',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'tx_events_domain_model_event' => [
|
||||||
|
0 => [
|
||||||
|
'uid' => '1',
|
||||||
|
'pid' => '2',
|
||||||
|
'title' => 'Title of Event',
|
||||||
|
'teaser' => 'Teaser of Event',
|
||||||
|
'keywords' => 'Gewölbe, Goethe, Horst Damm, Kästner, Theater',
|
||||||
|
'hidden' => '0',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
|
@ -4,9 +4,16 @@ declare(strict_types=1);
|
||||||
|
|
||||||
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
|
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
|
||||||
use WerkraumMedia\Events\Controller\DateController;
|
use WerkraumMedia\Events\Controller\DateController;
|
||||||
|
use WerkraumMedia\Events\Controller\EventController;
|
||||||
|
|
||||||
ExtensionUtility::configurePlugin(
|
ExtensionUtility::configurePlugin(
|
||||||
'Events',
|
'Events',
|
||||||
'DateListTest',
|
'DateListTest',
|
||||||
[DateController::class => 'list']
|
[DateController::class => 'list']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ExtensionUtility::configurePlugin(
|
||||||
|
'Events',
|
||||||
|
'EventShow',
|
||||||
|
[EventController::class => 'show']
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tx_events_domain_model_event' => [
|
||||||
|
[
|
||||||
|
'uid' => 1,
|
||||||
|
'global_id' => 'e_100347853',
|
||||||
|
'keywords' => 'Gewölbe, Goethe, Horst Damm, Kästner, Theater, low_image_qualit',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'uid' => 2,
|
||||||
|
'global_id' => 'e_100347854',
|
||||||
|
'keywords' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
|
@ -0,0 +1,153 @@
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"count": 2,
|
||||||
|
"overallcount": 2,
|
||||||
|
"channels": [],
|
||||||
|
"facetGroups": [],
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"global_id": "e_100347853",
|
||||||
|
"id": "100347853",
|
||||||
|
"title": "Allerlei Weihnachtliches (Heute mit Johannes Geißer)",
|
||||||
|
"type": "Event",
|
||||||
|
"categories": [
|
||||||
|
],
|
||||||
|
"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.<br>Eintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)<br>Um Voranmeldung unter 03672-486470 oder <a data-cke-saved-href=\"mailto:schillerhaus@rudolstadt.de\" href=\"mailto:schillerhaus@rudolstadt.de\">schillerhaus@rudolstadt.de</a> wird gebeten. <br><strong>Es gilt die 2G-PLUS-Regel.</strong> <br>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"media_objects": [
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"Gewölbe",
|
||||||
|
"Goethe",
|
||||||
|
"Horst Damm",
|
||||||
|
"Kästner",
|
||||||
|
"Theater",
|
||||||
|
"low_image_qualit"
|
||||||
|
],
|
||||||
|
"timeIntervals": [
|
||||||
|
{
|
||||||
|
"weekdays": [],
|
||||||
|
"start": "2099-12-19T15:00:00+01:00",
|
||||||
|
"end": "2099-12-19T16:30:00+01:00",
|
||||||
|
"tz": "Europe/Berlin",
|
||||||
|
"interval": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Schillerhaus Rudolstadt",
|
||||||
|
"features": [
|
||||||
|
],
|
||||||
|
"addresses": [
|
||||||
|
],
|
||||||
|
"created": "2099-10-31T12:29:00+00:00",
|
||||||
|
"changed": "2099-12-14T08:29:00+00:00",
|
||||||
|
"company": "",
|
||||||
|
"district": "",
|
||||||
|
"postoffice": "",
|
||||||
|
"phone2": "",
|
||||||
|
"seasons": [],
|
||||||
|
"subitems": [],
|
||||||
|
"hyperObjects": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"global_id": "e_100347854",
|
||||||
|
"id": "100347854",
|
||||||
|
"title": "Allerlei Weihnachtliches (Heute mit Johannes Geißer)",
|
||||||
|
"type": "Event",
|
||||||
|
"categories": [
|
||||||
|
],
|
||||||
|
"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.<br>Eintritt: 10 € (inkl. Gedeck mit weihnachtlicher Schillerlocke)<br>Um Voranmeldung unter 03672-486470 oder <a data-cke-saved-href=\"mailto:schillerhaus@rudolstadt.de\" href=\"mailto:schillerhaus@rudolstadt.de\">schillerhaus@rudolstadt.de</a> wird gebeten. <br><strong>Es gilt die 2G-PLUS-Regel.</strong> <br>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Schillerhaus Rudolstadt",
|
||||||
|
"features": [
|
||||||
|
],
|
||||||
|
"addresses": [
|
||||||
|
],
|
||||||
|
"created": "2099-10-31T12:29:00+00:00",
|
||||||
|
"changed": "2099-12-14T08:29:00+00:00",
|
||||||
|
"source": {
|
||||||
|
"url": "https://example.com",
|
||||||
|
"value": "Foreign Example 1"
|
||||||
|
},
|
||||||
|
"company": "",
|
||||||
|
"district": "",
|
||||||
|
"postoffice": "",
|
||||||
|
"phone2": "",
|
||||||
|
"seasons": [],
|
||||||
|
"subitems": [],
|
||||||
|
"hyperObjects": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -94,4 +94,18 @@ class ImportsExampleAsExpectedTest extends AbstractTestCase
|
||||||
$this->assertPHPDataSet(__DIR__ . '/Assertions/ImportsSource.php');
|
$this->assertPHPDataSet(__DIR__ . '/Assertions/ImportsSource.php');
|
||||||
$this->assertEmptyLog();
|
$this->assertEmptyLog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function importsKeywords(): void
|
||||||
|
{
|
||||||
|
$this->importPHPDataSet(__DIR__ . '/Fixtures/Database/DefaultImportConfiguration.php');
|
||||||
|
$this->setUpResponses([
|
||||||
|
new Response(200, [], file_get_contents(__DIR__ . '/Fixtures/ResponseWithKeywords.json') ?: ''),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->executeCommand();
|
||||||
|
|
||||||
|
$this->assertPHPDataSet(__DIR__ . '/Assertions/ImportsKeywords.php');
|
||||||
|
$this->assertEmptyLog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ CREATE TABLE tx_events_domain_model_event (
|
||||||
images int(11) unsigned NOT NULL default '0',
|
images int(11) unsigned NOT NULL default '0',
|
||||||
categories int(11) DEFAULT '0' NOT NULL,
|
categories int(11) DEFAULT '0' NOT NULL,
|
||||||
features int(11) DEFAULT '0' NOT NULL,
|
features int(11) DEFAULT '0' NOT NULL,
|
||||||
|
keywords text,
|
||||||
pages text,
|
pages text,
|
||||||
dates int(11) unsigned DEFAULT '0' NOT NULL,
|
dates int(11) unsigned DEFAULT '0' NOT NULL,
|
||||||
organizer int(11) unsigned DEFAULT '0',
|
organizer int(11) unsigned DEFAULT '0',
|
||||||
|
|
Loading…
Reference in a new issue