From fbf3610d68b9d1be0f87fa3035984c60ac1a6bc3 Mon Sep 17 00:00:00 2001 From: manuxi Date: Fri, 26 Mar 2021 13:02:07 +0100 Subject: [PATCH] git init --- .gitignore | 6 + .php_cs | 54 +++ LICENSE | 21 + README.md | 41 ++ composer.json | 41 ++ phpunit.xml.dist | 28 ++ src/Admin/EventAdmin.php | 231 +++++++++++ src/Admin/LocationAdmin.php | 95 +++++ .../DoctrineListRepresentationFactory.php | 64 +++ src/Content/EventDataItem.php | 51 +++ src/Content/EventDataProvider.php | 72 ++++ src/Content/Type/EventSelection.php | 53 +++ src/Content/Type/SingleEventSelection.php | 44 +++ src/Controller/Admin/EventController.php | 293 ++++++++++++++ src/Controller/Admin/LocationController.php | 147 +++++++ src/Controller/Website/AbstractController.php | 38 ++ src/Controller/Website/EventController.php | 172 ++++++++ src/DependencyInjection/Configuration.php | 35 ++ .../SuluEventExtension.php | 190 +++++++++ src/Entity/Event.php | 366 ++++++++++++++++++ src/Entity/EventSeo.php | 255 ++++++++++++ src/Entity/EventSeoTranslation.php | 122 ++++++ src/Entity/EventTranslation.php | 109 ++++++ src/Entity/Location.php | 130 +++++++ src/Repository/EventRepository.php | 180 +++++++++ src/Repository/EventSeoRepository.php | 98 +++++ .../EventSeoTranslationRepository.php | 25 ++ src/Repository/EventTranslationRepository.php | 25 ++ src/Repository/LocationRepository.php | 93 +++++ src/Resources/config/controller.xml | 51 +++ src/Resources/config/forms/event_details.xml | 58 +++ .../config/forms/location_details.xml | 53 +++ src/Resources/config/lists/events.xml | 107 +++++ src/Resources/config/lists/locations.xml | 41 ++ src/Resources/config/routes.yml | 17 + src/Resources/config/routes_admin.yml | 14 + src/Resources/config/services.xml | 69 ++++ .../config/translations/admin.de.yml | 19 + .../config/translations/admin.en.yml | 19 + src/Service/CountryCodeSelect.php | 27 ++ src/SuluEventBundle.php | 10 + 41 files changed, 3564 insertions(+) create mode 100644 .gitignore create mode 100644 .php_cs create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/Admin/EventAdmin.php create mode 100644 src/Admin/LocationAdmin.php create mode 100644 src/Common/DoctrineListRepresentationFactory.php create mode 100644 src/Content/EventDataItem.php create mode 100644 src/Content/EventDataProvider.php create mode 100644 src/Content/Type/EventSelection.php create mode 100644 src/Content/Type/SingleEventSelection.php create mode 100644 src/Controller/Admin/EventController.php create mode 100644 src/Controller/Admin/LocationController.php create mode 100644 src/Controller/Website/AbstractController.php create mode 100644 src/Controller/Website/EventController.php create mode 100644 src/DependencyInjection/Configuration.php create mode 100644 src/DependencyInjection/SuluEventExtension.php create mode 100644 src/Entity/Event.php create mode 100644 src/Entity/EventSeo.php create mode 100644 src/Entity/EventSeoTranslation.php create mode 100644 src/Entity/EventTranslation.php create mode 100644 src/Entity/Location.php create mode 100644 src/Repository/EventRepository.php create mode 100644 src/Repository/EventSeoRepository.php create mode 100644 src/Repository/EventSeoTranslationRepository.php create mode 100644 src/Repository/EventTranslationRepository.php create mode 100644 src/Repository/LocationRepository.php create mode 100644 src/Resources/config/controller.xml create mode 100644 src/Resources/config/forms/event_details.xml create mode 100644 src/Resources/config/forms/location_details.xml create mode 100644 src/Resources/config/lists/events.xml create mode 100644 src/Resources/config/lists/locations.xml create mode 100644 src/Resources/config/routes.yml create mode 100644 src/Resources/config/routes_admin.yml create mode 100644 src/Resources/config/services.xml create mode 100644 src/Resources/config/translations/admin.de.yml create mode 100644 src/Resources/config/translations/admin.en.yml create mode 100644 src/Service/CountryCodeSelect.php create mode 100644 src/SuluEventBundle.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50b5566 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/vendor/ +composer.lock +/phpunit.xml +/tests/cache +/var/cache +.phpunit.result.cache diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..d99d57a --- /dev/null +++ b/.php_cs @@ -0,0 +1,54 @@ +in([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); +; +return PhpCsFixer\Config::create() + //->setUsingCache(false) + ->setRiskyAllowed(true) + //->setIndent(" ") + //->setLineEnding("CRLF") + //->setLineEnding("\r\n") + //->setLineEnding(PHP_EOL) + ->setFinder($finder) + ->setCacheFile(__DIR__.'/var/cache/.php_cs.cache') + ->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + 'align_multiline_comment' => [ + 'comment_type' => 'phpdocs_like', + ], + 'array_indentation' => true, + 'array_syntax' => [ + 'syntax' => 'short' + ], + 'binary_operator_spaces' => [ + 'default' => 'align' + ], + 'blank_line_after_namespace' => true, + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => false, + 'concat_space' => [ + 'spacing' => 'one' + ], + 'linebreak_after_opening_tag' => true, + 'mb_str_functions' => true, + 'no_php4_constructor' => true, + 'native_function_invocation' => true, + 'no_superfluous_phpdoc_tags' => true, + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_imports' => true, + 'php_unit_strict' => true, + 'phpdoc_order' => true, + 'semicolon_after_instruction' => true, + 'strict_comparison' => true, + 'strict_param' => true, + ]) +; diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3defe56 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 manuxi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..da6e323 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +Installation +============ + +Make sure Composer is installed globally, as explained in the +[installation chapter](https://getcomposer.org/doc/00-intro.md) +of the Composer documentation. + +Applications that use Symfony Flex +---------------------------------- + +Open a command console, enter your project directory and execute: + +```console +$ composer require +``` + +Applications that don't use Symfony Flex +---------------------------------------- + +### Step 1: Download the Bundle + +Open a command console, enter your project directory and execute the +following command to download the latest stable version of this bundle: + +```console +$ composer require +``` + +### Step 2: Enable the Bundle + +Then, enable the bundle by adding it to the list of registered bundles +in the `config/bundles.php` file of your project: + +```php +// config/bundles.php + +return [ + // ... + \\::class => ['all' => true], +]; +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b0757c2 --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "name": "manuxi/sulu-event-bundle", + "description": "Say hello to sulu-events", + "type": "symfony-bundle", + "license": "MIT", + "authors": [ + { + "name": "Manuel Bertrams", + "email": "SirHonk@gmail.com" + } + ], + "require": { + "dantleech/phpcr-migrations-bundle": "^1.2", + "php": "^7.1.3", + "sulu/sulu": "^2.2", + "symfony/config": "^4.0 | ^5.0", + "symfony/dependency-injection": "^4.0 || ^5.0", + "symfony/framework-bundle": "^4.0 | ^5.0", + "symfony/http-foundation": "^4.0 | ^5.0", + "symfony/http-kernel": "^4.0 | ^5.0", + "cocur/slugify": "^4.0", + "symfony/translation": "^5.2", + "symfony/intl": "^5.2" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.0 | ^5.0", + "symfony/framework-bundle": "^4.0 | ^5.0", + "symfony/browser-kit": "^4.0 | ^5.0", + "jackalope/jackalope-doctrine-dbal": "^1.3.4" + }, + "autoload": { + "psr-4": { + "Manuxi\\SuluEventBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Manuxi\\SuluEventBundle\\Tests\\": "tests/" + } + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..7df5878 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + ./tests + + + + + + ./src + + + diff --git a/src/Admin/EventAdmin.php b/src/Admin/EventAdmin.php new file mode 100644 index 0000000..f3ff935 --- /dev/null +++ b/src/Admin/EventAdmin.php @@ -0,0 +1,231 @@ +viewBuilderFactory = $viewBuilderFactory; + $this->securityChecker = $securityChecker; + $this->webspaceManager = $webspaceManager; + } + + public function configureNavigationItems(NavigationItemCollection $navigationItemCollection): void + { + if ($this->securityChecker->hasPermission(Event::SECURITY_CONTEXT, PermissionTypes::EDIT)) { + $rootNavigationItem = new NavigationItem('app.events'); + $rootNavigationItem->setIcon('su-calendar'); + $rootNavigationItem->setPosition(30); + $rootNavigationItem->setView(static::LIST_VIEW); + + // Configure a NavigationItem with a View + $eventNavigationItem = new NavigationItem('app.events'); + $eventNavigationItem->setPosition(10); + $eventNavigationItem->setView(static::LIST_VIEW); + + $rootNavigationItem->addChild($eventNavigationItem); + + $navigationItemCollection->add($rootNavigationItem); + } + } + + public function configureViews(ViewCollection $viewCollection): void + { + $formToolbarActions = []; + $listToolbarActions = []; + + $locales = $this->webspaceManager->getAllLocales(); + + if ($this->securityChecker->hasPermission(Event::SECURITY_CONTEXT, PermissionTypes::ADD)) { + $listToolbarActions[] = new ToolbarAction('sulu_admin.add'); + } + + if ($this->securityChecker->hasPermission(Event::SECURITY_CONTEXT, PermissionTypes::EDIT)) { + $formToolbarActions[] = new ToolbarAction('sulu_admin.save'); + } + + if ($this->securityChecker->hasPermission(Event::SECURITY_CONTEXT, PermissionTypes::DELETE)) { + $formToolbarActions[] = new ToolbarAction('sulu_admin.delete'); + $listToolbarActions[] = new ToolbarAction('sulu_admin.delete'); + } + + if ($this->securityChecker->hasPermission(Event::SECURITY_CONTEXT, PermissionTypes::VIEW)) { + $listToolbarActions[] = new ToolbarAction('sulu_admin.export'); + } + + if ($this->securityChecker->hasPermission(Event::SECURITY_CONTEXT, PermissionTypes::EDIT)) { + // Configure Event List View + $listView = $this->viewBuilderFactory->createListViewBuilder(static::LIST_VIEW, '/events/:locale') + ->setResourceKey(Event::RESOURCE_KEY) + ->setListKey(Event::LIST_KEY) + ->setTitle('app.events') + ->addListAdapters(['table']) + ->addLocales($locales) + ->setDefaultLocale($locales[0]) + ->setAddView(static::ADD_FORM_VIEW) + ->setEditView(static::EDIT_FORM_VIEW) + ->addToolbarActions($listToolbarActions); + $viewCollection->add($listView); + + // Configure Event Add View + $addFormView = $this->viewBuilderFactory->createResourceTabViewBuilder(static::ADD_FORM_VIEW, '/events/:locale/add') + ->setResourceKey(Event::RESOURCE_KEY) + ->setBackView(static::LIST_VIEW) + ->addLocales($locales); + $viewCollection->add($addFormView); + + $addDetailsFormView = $this->viewBuilderFactory->createFormViewBuilder(static::ADD_FORM_DETAILS_VIEW, '/details') + ->setResourceKey(Event::RESOURCE_KEY) + ->setFormKey(Event::FORM_KEY) + ->setTabTitle('sulu_admin.details') + ->setEditView(static::EDIT_FORM_VIEW) + ->addToolbarActions($formToolbarActions) + ->setParent(static::ADD_FORM_VIEW); + $viewCollection->add($addDetailsFormView); + + // Configure Event Edit View + $editFormView = $this->viewBuilderFactory->createResourceTabViewBuilder(static::EDIT_FORM_VIEW, '/events/:locale/:id') + ->setResourceKey(Event::RESOURCE_KEY) + ->setBackView(static::LIST_VIEW) + ->setTitleProperty('title') + ->addLocales($locales); + $viewCollection->add($editFormView); + + //enable/disable toolbar actions + $formToolbarActions = [ + new ToolbarAction('sulu_admin.save'), + new ToolbarAction('sulu_admin.delete'), + new TogglerToolbarAction( + 'app.enable_event', + 'enabled', + 'enable', + 'disable' + ), + ]; + + $editDetailsFormView = $this->viewBuilderFactory->createFormViewBuilder(static::EDIT_FORM_DETAILS_VIEW, '/details') + ->setResourceKey(Event::RESOURCE_KEY) + ->setFormKey(Event::FORM_KEY) + ->setTabTitle('sulu_admin.details') + ->addToolbarActions($formToolbarActions) + ->setParent(static::EDIT_FORM_VIEW); + $viewCollection->add($editDetailsFormView); + + //seo,excerpt, etc + $formToolbarActionsWithoutType = []; + $previewCondition = 'nodeType == 1'; + + if ($this->securityChecker->hasPermission(static::SECURITY_CONTEXT, PermissionTypes::ADD) + && $this->securityChecker->hasPermission(static::SECURITY_CONTEXT, PermissionTypes::ADD)) { + $listToolbarActions[] = new ToolbarAction('sulu_admin.add'); + } + + if ($this->securityChecker->hasPermission(static::SECURITY_CONTEXT, PermissionTypes::LIVE)) { + $formToolbarActionsWithoutType[] = new ToolbarAction('sulu_admin.save_with_publishing'); + } else { + $formToolbarActionsWithoutType[] = new ToolbarAction('sulu_admin.save'); + } + +// $viewCollection->add( +// $this->viewBuilderFactory->createPreviewFormViewBuilder(static::EDIT_FORM_VIEW_EXCERPT, '/excerpt') +// ->setResourceKey(Event::RESOURCE_KEY) +// ->setFormKey('page_excerpt') +// ->setTabTitle('sulu_page.excerpt') +// ->addToolbarActions($formToolbarActionsWithoutType) +// ->setParent(static::EDIT_FORM_VIEW) +// ); + + $viewCollection->add( + $this->viewBuilderFactory + ->createPreviewFormViewBuilder(static::EDIT_FORM_VIEW_SEO, '/seo') + ->disablePreviewWebspaceChooser() + ->setResourceKey(Event::RESOURCE_KEY) + ->setFormKey('page_seo') + ->setTabTitle('sulu_page.seo') +// ->setTabCondition('nodeType == 1 && shadowOn == false') + ->addToolbarActions($formToolbarActionsWithoutType) +// ->addRouterAttributesToFormRequest($routerAttributesToFormRequest) + ->setPreviewCondition($previewCondition) + ->setTitleVisible(true) + ->setTabOrder(2048) + ->setParent(static::EDIT_FORM_VIEW) + ); + $viewCollection->add( + $this->viewBuilderFactory + ->createPreviewFormViewBuilder(static::EDIT_FORM_VIEW_EXCERPT, '/excerpt') + ->disablePreviewWebspaceChooser() + ->setResourceKey(Event::RESOURCE_KEY) + ->setFormKey('page_excerpt') + ->setTabTitle('sulu_page.excerpt') +// ->setTabCondition('(nodeType == 1 || nodeType == 4) && shadowOn == false') + ->addToolbarActions($formToolbarActionsWithoutType) +// ->addRouterAttributesToFormRequest($routerAttributesToFormRequest) +// ->addRouterAttributesToFormMetadata($routerAttributesToFormMetadata) + ->setPreviewCondition($previewCondition) + ->setTitleVisible(true) + ->setTabOrder(3072) + ->setParent(static::EDIT_FORM_VIEW) + ); + } + } + + /** + * @return mixed[] + */ + public function getSecurityContexts(): array + { + return [ + self::SULU_ADMIN_SECURITY_SYSTEM => [ + 'Events' => [ + Event::SECURITY_CONTEXT => [ + PermissionTypes::VIEW, + PermissionTypes::ADD, + PermissionTypes::EDIT, + PermissionTypes::DELETE, + ], + ], + ], + ]; + } +} diff --git a/src/Admin/LocationAdmin.php b/src/Admin/LocationAdmin.php new file mode 100644 index 0000000..0fb6861 --- /dev/null +++ b/src/Admin/LocationAdmin.php @@ -0,0 +1,95 @@ +viewBuilderFactory = $viewBuilderFactory; + } + + /** + * @throws NavigationItemNotFoundException + */ + public function configureNavigationItems(NavigationItemCollection $navigationItemCollection): void + { + $module = $navigationItemCollection->get('app.events'); + $locations = new NavigationItem('app.locations'); + $locations->setPosition(10); + $locations->setView(static::LOCATION_LIST_VIEW); + + $module->addChild($locations); + } + + public function configureViews(ViewCollection $viewCollection): void + { + $listToolbarActions = [ + new ToolbarAction('sulu_admin.add'), + new ToolbarAction('sulu_admin.delete'), + ]; + + $listView = $this->viewBuilderFactory->createListViewBuilder(self::LOCATION_LIST_VIEW, '/locations') + ->setResourceKey(Location::RESOURCE_KEY) + ->setListKey(self::LOCATION_LIST_KEY) + ->setTitle('app.locations') + ->addListAdapters(['table']) + ->setAddView(static::LOCATION_ADD_FORM_VIEW) + ->setEditView(static::LOCATION_EDIT_FORM_VIEW) + ->addToolbarActions($listToolbarActions); + $viewCollection->add($listView); + + $addFormView = $this->viewBuilderFactory->createResourceTabViewBuilder(self::LOCATION_ADD_FORM_VIEW, '/locations/add') + ->setResourceKey('locations') + ->setBackView(static::LOCATION_LIST_VIEW); + $viewCollection->add($addFormView); + + $addDetailsFormView = $this->viewBuilderFactory->createFormViewBuilder(self::LOCATION_ADD_FORM_VIEW . '.details', '/details') + ->setResourceKey('locations') + ->setFormKey('location_details') + ->setTabTitle('sulu_admin.details') + ->setEditView(static::LOCATION_EDIT_FORM_VIEW) + ->addToolbarActions([new ToolbarAction('sulu_admin.save')]) + ->setParent(static::LOCATION_ADD_FORM_VIEW); + $viewCollection->add($addDetailsFormView); + + $editFormView = $this->viewBuilderFactory->createResourceTabViewBuilder(static::LOCATION_EDIT_FORM_VIEW, '/locations/:id') + ->setResourceKey('locations') + ->setBackView(static::LOCATION_LIST_VIEW) + ->setTitleProperty('title'); + $viewCollection->add($editFormView); + + $formToolbarActions = [ + new ToolbarAction('sulu_admin.save'), + new ToolbarAction('sulu_admin.delete'), + ]; + $editDetailsFormView = $this->viewBuilderFactory->createFormViewBuilder(static::LOCATION_EDIT_FORM_VIEW . '.details', '/details') + ->setResourceKey('locations') + ->setFormKey('location_details') + ->setTabTitle('sulu_admin.details') + ->addToolbarActions($formToolbarActions) + ->setParent(static::LOCATION_EDIT_FORM_VIEW); + $viewCollection->add($editDetailsFormView); + } +} diff --git a/src/Common/DoctrineListRepresentationFactory.php b/src/Common/DoctrineListRepresentationFactory.php new file mode 100644 index 0000000..9cc1644 --- /dev/null +++ b/src/Common/DoctrineListRepresentationFactory.php @@ -0,0 +1,64 @@ +restHelper = $restHelper; + $this->listBuilderFactory = $listBuilderFactory; + $this->fieldDescriptorFactory = $fieldDescriptorFactory; + } + + /** + * @param mixed[] $filters + * @param mixed[] $parameters + */ + public function createDoctrineListRepresentation( + string $resourceKey, + array $filters = [], + array $parameters = [] + ): PaginatedRepresentation { + /** @var DoctrineFieldDescriptor[] $fieldDescriptors */ + $fieldDescriptors = $this->fieldDescriptorFactory->getFieldDescriptors($resourceKey); + + $listBuilder = $this->listBuilderFactory->create($fieldDescriptors['id']->getEntityName()); + $this->restHelper->initializeListBuilder($listBuilder, $fieldDescriptors); + + foreach ($parameters as $key => $value) { + $listBuilder->setParameter($key, $value); + } + + foreach ($filters as $key => $value) { + $listBuilder->where($fieldDescriptors[$key], $value); + } + + $list = $listBuilder->execute(); + + return new PaginatedRepresentation( + $list, + $resourceKey, + (int) $listBuilder->getCurrentPage(), + (int) $listBuilder->getLimit(), + (int) $listBuilder->count() + ); + } +} diff --git a/src/Content/EventDataItem.php b/src/Content/EventDataItem.php new file mode 100644 index 0000000..da5e38b --- /dev/null +++ b/src/Content/EventDataItem.php @@ -0,0 +1,51 @@ +entity = $entity; + } + + /** + * @Serializer\VirtualProperty + */ + public function getId(): string + { + return (string) $this->entity->getId(); + } + + /** + * @Serializer\VirtualProperty + */ + public function getTitle(): string + { + return (string) $this->entity->getTitle(); + } + + /** + * @Serializer\VirtualProperty + */ + public function getImage(): ?string + { + return null; + } + + public function getResource(): Event + { + return $this->entity; + } +} diff --git a/src/Content/EventDataProvider.php b/src/Content/EventDataProvider.php new file mode 100644 index 0000000..2639422 --- /dev/null +++ b/src/Content/EventDataProvider.php @@ -0,0 +1,72 @@ +configuration) { + $this->configuration = self::createConfigurationBuilder() + ->enableLimit() + ->enablePagination() + ->enableSorting( + [ + ['column' => 'entity.title', 'title' => 'sulu_admin.title'], + ['column' => 'entity.startDate', 'title' => 'app.start_date'], + ] + ) + ->getConfiguration(); + } + + return parent::getConfiguration(); + } + + /** + * @param mixed[] $data + * @return array + */ + protected function decorateDataItems(array $data): array + { + return \array_map( + static function ($item) { + return new EventDataItem($item); + }, + $data + ); + } + + /** + * Returns flag "hasNextPage". + * It combines the limit/query-count with the page and page-size. + * + * @noinspection PhpUnusedPrivateMethodInspection + * @param \Countable $queryResult + * @param int|null $limit + * @param int $page + * @param int|null $pageSize + * @return bool + */ + private function hasNextPage(\Countable $queryResult, ?int $limit, int $page, ?int $pageSize): bool + { + $count = $queryResult->count(); + + if (null === $pageSize || $pageSize > $this->defaultLimit) { + $pageSize = $this->defaultLimit; + } + + $offset = ($page - 1) * $pageSize; + if ($limit && $offset + $pageSize > $limit) { + return false; + } + + return $count > ($page * $pageSize); + } +} diff --git a/src/Content/Type/EventSelection.php b/src/Content/Type/EventSelection.php new file mode 100644 index 0000000..afa458d --- /dev/null +++ b/src/Content/Type/EventSelection.php @@ -0,0 +1,53 @@ +entityManager = $entityManager; + + parent::__construct('event_selection', []); + } + + /** + * @return Event[] + */ + public function getContentData(PropertyInterface $property): array + { + $ids = $property->getValue(); + + if (empty($ids)) { + return []; + } + + $events = $this->entityManager->getRepository(Event::class)->findBy(['id' => $ids]); + + $idPositions = \array_flip($ids); + \usort($events, static function (Event $a, Event $b) use ($idPositions) { + return $idPositions[$a->getId()] - $idPositions[$b->getId()]; + }); + + return $events; + } + + /** + * @return array|null> + */ + public function getViewData(PropertyInterface $property): array + { + return [ + 'ids' => $property->getValue(), + ]; + } +} diff --git a/src/Content/Type/SingleEventSelection.php b/src/Content/Type/SingleEventSelection.php new file mode 100644 index 0000000..bad18b7 --- /dev/null +++ b/src/Content/Type/SingleEventSelection.php @@ -0,0 +1,44 @@ +entityManager = $entityManager; + + parent::__construct('single_event_selection'); + } + + public function getContentData(PropertyInterface $property): ?Event + { + $id = $property->getValue(); + + if (empty($id)) { + return null; + } + + return $this->entityManager->getRepository(Event::class)->find($id); + } + + /** + * @param PropertyInterface $property + * @return array + */ + public function getViewData(PropertyInterface $property): array + { + return [ + 'id' => $property->getValue(), + ]; + } +} diff --git a/src/Controller/Admin/EventController.php b/src/Controller/Admin/EventController.php new file mode 100644 index 0000000..4ea2bae --- /dev/null +++ b/src/Controller/Admin/EventController.php @@ -0,0 +1,293 @@ +eventRepository = $eventRepository; + $this->eventSeoRepository = $eventSeoRepository; + $this->mediaRepository = $mediaRepository; + $this->doctrineListRepresentationFactory = $doctrineListRepresentationFactory; + $this->entityManager = $entityManager; + $this->locationRepository = $locationRepository; + + parent::__construct($viewHandler, $tokenStorage); + } + + public function cgetAction(Request $request): Response + { + $locale = $request->query->get('locale'); + $listRepresentation = $this->doctrineListRepresentationFactory->createDoctrineListRepresentation( + Event::RESOURCE_KEY, + [], + ['locale' => $locale] + ); + + return $this->handleView($this->view($listRepresentation)); + } + + public function getAction(int $id, Request $request): Response + { + $entity = $this->loadEvent($id, $request); + if (!$entity) { + throw new NotFoundHttpException(); + } + + return $this->handleView($this->view($entity)); + } + + /** + * @throws \Exception + */ + public function postAction(Request $request): Response + { + //event + $eventEntity = $this->createEvent($request); + $this->mapDataToEntity($request->request->all(), $eventEntity); + $this->saveEvent($eventEntity); + + //in the first step we'll _only_ create a Event object (no Seo, Taxonomy, etc.) + + return $this->handleView($this->view($eventEntity, 201)); + } + + /** + * @Rest\Post("/events/{id}") + * + * @throws ORMException + * @throws OptimisticLockException + */ + public function postTriggerAction(int $id, Request $request): Response + { + $event = $this->eventRepository->findById($id, (string) $this->getLocale($request)); + if (!$event) { + throw new NotFoundHttpException(); + } + + switch ($request->query->get('action')) { + case 'enable': + $event->setEnabled(true); + break; + case 'disable': + $event->setEnabled(false); + break; + } + + $this->eventRepository->save($event); + + return $this->handleView($this->view($event)); + } + + /** + * @throws \Exception + */ + public function putAction(int $id, Request $request): Response + { + $eventEntity = $this->loadEvent($id, $request); + if (!$eventEntity) { + throw new NotFoundHttpException(); + } + + $this->mapDataToEntity($request->request->all(), $eventEntity); + $this->saveEvent($eventEntity); + + //do we have an EventSeo? + $eventSeoEntity = $eventEntity->getEventSeo(); + $this->mapDataToEventSeoEntity($request->request->all(), $eventSeoEntity); + $this->saveEventSeo($eventSeoEntity); + + return $this->handleView($this->view($eventEntity)); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function deleteAction(int $id): Response + { + $this->removeEvent($id); + + return $this->handleView($this->view(null, 204)); + } + + public function getSecurityContext(): string + { + return Event::SECURITY_CONTEXT; + } + + /** + * @throws \Exception + */ + protected function mapDataToEntity(array $data, Event $entity): void + { + $entity->setTitle($data['title']); + + $teaser = $data['teaser'] ?? null; + if ($teaser) { + $entity->setTeaser($teaser); + } + + $description = $data['description'] ?? null; + if ($description) { + $entity->setDescription($description); + } + + $image = null; + $imageId = ($data['image']['id'] ?? null); + if ($imageId) { + $image = $this->mediaRepository->findMediaById($imageId); + $entity->setImage($image); + } + + $startDate = $data['startDate'] ?? null; + if ($startDate) { + $entity->setStartDate(new \DateTimeImmutable($startDate)); + } + + $endDate = $data['endDate'] ?? null; + if ($endDate) { + $entity->setEndDate(new \DateTimeImmutable($endDate)); + } + + $locationId = $data['locationId'] ?? null; + if ($locationId) { + $entity->setLocation( + $this->locationRepository->findById((int) $locationId) + ); + } + } + + /** + * @param array $data + */ + protected function mapDataToEventSeoEntity(array $data, EventSeo $entity): void + { + $title = $data['ext']['seo']['title'] ?? null; + $entity->setTitle($title); + + $description = $data['ext']['seo']['description'] ?? null; + $entity->setDescription($description); + + $keywords = $data['ext']['seo']['keywords'] ?? null; + $entity->setKeywords($keywords); + + $canonicalUrl = $data['ext']['seo']['canonicalUrl'] ?? null; + $entity->setCanonicalUrl($canonicalUrl); + + $noIndex = $data['ext']['seo']['noIndex'] ?? false; + $entity->setNoIndex($noIndex); + + $noFollow = $data['ext']['seo']['noFollow'] ?? false; + $entity->setNoFollow($noFollow); + + $hideInSitemap = $data['ext']['seo']['hideInSitemap'] ?? false; + $entity->setHideInSitemap($hideInSitemap); + } + + protected function loadEvent(int $id, Request $request): ?Event + { + return $this->eventRepository->findById($id, (string) $this->getLocale($request)); + } + + protected function createEvent(Request $request): Event + { + return $this->eventRepository->create((string) $this->getLocale($request)); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + protected function saveEvent(Event $entity): void + { + $this->eventRepository->save($entity); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + protected function removeEvent(int $id): void + { + $this->eventRepository->remove($id); + } + + /** @noinspection PhpUnused */ + protected function loadEventSeo(int $id, Request $request): ?EventSeo + { + return $this->eventSeoRepository->findById($id, (string) $this->getLocale($request)); + } + + protected function createEventSeo(Request $request): EventSeo + { + return $this->eventSeoRepository->create((string) $this->getLocale($request)); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + protected function saveEventSeo(EventSeo $entity): void + { + $this->eventSeoRepository->save($entity); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + protected function removeEventSeo(int $id): void + { + $this->eventSeoRepository->remove($id); + } +} diff --git a/src/Controller/Admin/LocationController.php b/src/Controller/Admin/LocationController.php new file mode 100644 index 0000000..da71bcb --- /dev/null +++ b/src/Controller/Admin/LocationController.php @@ -0,0 +1,147 @@ +doctrineListRepresentationFactory = $doctrineListRepresentationFactory; + $this->repository = $repository; + + parent::__construct($viewHandler, $tokenStorage); + } + + public function cgetAction(): Response + { + $listRepresentation = $this->doctrineListRepresentationFactory->createDoctrineListRepresentation( + Location::RESOURCE_KEY + ); + + return $this->handleView($this->view($listRepresentation)); + } + + /** @noinspection PhpUnusedParameterInspection */ + public function getAction(int $id, Request $request): Response + { + $entity = $this->load($id); + if (!$entity) { + throw new NotFoundHttpException(); + } + + return $this->handleView($this->view($entity)); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function postAction(Request $request): Response + { + $entity = $this->create(); + + $this->mapDataToEntity($request->request->all(), $entity); + + $this->save($entity); + + return $this->handleView($this->view($entity)); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function putAction(int $id, Request $request): Response + { + $entity = $this->load($id); + if (!$entity) { + throw new NotFoundHttpException(); + } + + $this->mapDataToEntity($request->request->all(), $entity); + + $this->save($entity); + + return $this->handleView($this->view($entity)); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function deleteAction(int $id): Response + { + $this->remove($id); + + return $this->handleView($this->view()); + } + + /** + * @param string[] $data + */ + protected function mapDataToEntity(array $data, Location $entity): void + { + $entity->setName($data['name']); + + $entity->setStreet($data['street'] ?? null); + $entity->setNumber($data['number'] ?? null); + $entity->setCity($data['city'] ?? null); + $entity->setPostalCode($data['postalCode'] ?? null); + $entity->setCountryCode($data['countryCode'] ?? null); + } + + protected function load(int $id): ?Location + { + return $this->repository->findById($id); + } + + protected function create(): Location + { + return $this->repository->create(); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + protected function save(Location $entity): void + { + $this->repository->save($entity); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + protected function remove(int $id): void + { + $this->repository->remove($id); + } +} diff --git a/src/Controller/Website/AbstractController.php b/src/Controller/Website/AbstractController.php new file mode 100644 index 0000000..1fbd8ad --- /dev/null +++ b/src/Controller/Website/AbstractController.php @@ -0,0 +1,38 @@ +request = $requestStack->getCurrentRequest(); + $this->mediaManager = $mediaManager; + } + + protected function getBannerMedia(array $bannerIds): array + { + $media = []; + foreach ($bannerIds as $id) { + $mediaEntity = $this->mediaManager->getEntityById($id); + $media[] = $this->mediaManager->addFormatsAndUrl(new Media($mediaEntity, $this->request->getLocale(), null)); + } + + return $media; + } +} diff --git a/src/Controller/Website/EventController.php b/src/Controller/Website/EventController.php new file mode 100644 index 0000000..b455109 --- /dev/null +++ b/src/Controller/Website/EventController.php @@ -0,0 +1,172 @@ +eventRepository = $eventRepository; + $this->webspaceManager = $webspaceManager; + $this->slugify = $slugify; + $this->translator = $translator; + $this->templateAttributeResolver = $templateAttributeResolver; + } + + /** + * @Route({ + * "en": "/events/{id}/{slug}", + * "de": "/veranstaltungen/{id}/{slug}" + * }, name="event") + */ + public function indexAction(int $id, string $slug): Response + { + $event = $this->eventRepository->findById($id, $this->request->getLocale()); + $parameters = $this->templateAttributeResolver->resolve([ + 'event' => $event, + 'content' => [ + 'title' => $this->translator->trans('pages.events'), + 'subtitle' => $event->getTitle(), + // 'banners' => $this->getBannerMedia(self::BANNER_IDS) //lets unittests fail + ], + 'path' => $this->generateUrl('event', ['id' => $id, 'slug' => $slug]), + 'extension' => $this->extractExtension($event), + 'localizations' => $this->getLocalizationsArrayForEntity($event), + 'created' => $event->getCreated(), + ]); + + return $this->render('pages/event.html.twig', $parameters); + } + + /** + * @return array + */ + protected function getLocalizationsArrayForEntity(Event $event): array + { + $locales = $this->webspaceManager->getAllLocales(); + + $localizations = []; + foreach ($locales as $locale) { + $event->setLocale($locale); + + //we don't have a translation + if (null === $event->getTitle()) { + $url = null; + } else { + $urlParams = [ + 'id' => $event->getId(), + 'slug' => $this->slugify->slugify($event->getTitle()), + '_locale' => $locale, + ]; + $routePath = $this->generateUrl('event', $urlParams); + $url = $this->webspaceManager->findUrlByResourceLocator( + $routePath, + null, + $locale + ); + } + + $localizations[$locale] = [ + 'locale' => $locale, + 'url' => $url, + ]; + } + + //we have to set back the locale of the event, because $event is passed by reference. + //We don't want to clone here so just reset the $event's locale... + $event->setLocale($this->request->getLocale()); + + return $localizations; + } + + private function extractExtension(Event $event): array + { + $serializer = SerializerBuilder::create()->build(); + + return $serializer->toArray($event->getExt()); + } + + /** + * @return string[] + */ +// public static function getSubscribedServices() +// { +// $subscribedServices = parent::getSubscribedServices(); +// + //// $subscribedServices['sulu_core.webspace.webspace_manager'] = WebspaceManagerInterface::class; + //// $subscribedServices['sulu.repository.route'] = RouteRepositoryInterface::class; +// $subscribedServices['sulu_website.resolver.template_attribute'] = TemplateAttributeResolverInterface::class; +// +// return $subscribedServices; +// } + + /** + * "seo" => array:7 [▼ + * "title" => "" + * "description" => "" + * "keywords" => "" + * "canonicalUrl" => "" + * "noIndex" => false + * "noFollow" => false + * "hideInSitemap" => false + * ]. + * + * @noinspection PhpUnusedPrivateMethodInspection + */ + private function getSeo(Event $event): array + { + return [$event]; + } + + /** + * "excerpt" => array:8 [▼ + * "title" => "" + * "more" => "" + * "description" => "" + * "categories" => [] + * "tags" => [] + * "segments" => [] + * "icon" => [] + * "images" => [] + * ]. + * + * @noinspection PhpUnusedPrivateMethodInspection + */ + private function getExcerpt(Event $event): array + { + return [$event]; + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..0cb244d --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,35 @@ +getRootNode(); + +// $root->children() +// ->arrayNode('objects') +// ->addDefaultsIfNotSet() +// ->children() +// ->arrayNode('news') +// ->addDefaultsIfNotSet() +// ->children() +// ->scalarNode('model')->defaultValue(Event::class)->end() +// ->scalarNode('repository')->defaultValue(EventRepository::class)->end() +// ->end() +// ->end() +// ->end() +// ->end() +// ->end(); + + return $treeBuilder; + } +} diff --git a/src/DependencyInjection/SuluEventExtension.php b/src/DependencyInjection/SuluEventExtension.php new file mode 100644 index 0000000..638df1f --- /dev/null +++ b/src/DependencyInjection/SuluEventExtension.php @@ -0,0 +1,190 @@ +processConfiguration($configuration, $configs); + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader->load('services.xml'); + $loader->load('controller.xml'); + + +// $this->configurePersistence($config['objects'], $container); + } + + public function prepend(ContainerBuilder $container) + { +// if ($container->hasExtension('sulu_search')) { +// $container->prependExtensionConfig( +// 'sulu_search', +// [ +// 'indexes' => [ +// 'contact' => [ +// 'name' => 'Event', +// 'icon' => 'su-calendar', +// 'view' => [ +// 'name' => EventAdmin::EDIT_FORM_VIEW, +// 'result_to_view' => [ +// 'id' => 'id', +// 'locale' => 'locale', +// ], +// ], +// 'security_context' => EventAdmin::SECURITY_CONTEXT, +// ], +// ], +// ] +// ); +// } + +// if ($container->hasExtension('sulu_route')) { +// $container->prependExtensionConfig( +// 'sulu_route', +// [ +// 'mappings' => [ +// Event::class => [ +// 'generator' => 'schema', +// 'options' => ['route_schema' => '/event/{object.getId()}'], +// 'resource_key' => Event::RESOURCE_KEY, +// ], +// ], +// ] +// ); +// } + + if ($container->hasExtension('sulu_admin')) { + $container->prependExtensionConfig( + 'sulu_admin', + [ + 'lists' => [ + 'directories' => [ + __DIR__ . '/../Resources/config/lists', + ], + ], + 'forms' => [ + 'directories' => [ + __DIR__ . '/../Resources/config/forms', + ], + ], + 'resources' => [ + 'events' => [ + 'routes' => [ + 'list' => 'app.get_events', + 'detail' => 'app.get_event', + ], + ], + 'locations' => [ + 'routes' => [ + 'list' => 'app.get_locations', + 'detail' => 'app.get_location', + ], + ], + ], + 'field_type_options' => [ + 'selection' => [ + 'event_selection' => [ + 'default_type' => 'list_overlay', + 'resource_key' => 'events', + 'view' => [ + 'name' => 'app.event_edit_form', + 'result_to_view' => [ + 'id' => 'id' + ] + ], + 'types' => [ + 'list_overlay' => [ + 'adapter' => 'table', + 'list_key' => 'events', + 'display_properties' => [ + 'title' + ], + 'icon' => 'su-calendar', + 'label' => 'app.events', + 'overlay_title' => 'app.events' + ] + ] + ] + ], + 'single_selection' => [ + 'single_event_selection' => [ + 'default_type' => 'list_overlay', + 'resource_key' => 'events', + 'view' => [ + 'name' => 'app.event_edit_form', + 'result_to_view' => [ + 'id' => 'id' + ] + ], + 'types' => [ + 'list_overlay' => [ + 'adapter' => 'table', + 'list_key' => 'events', + 'display_properties' => [ + 'title' + ], + 'icon' => 'su-calendar', + 'empty_text' => 'app.events.no_selections', + 'overlay_title' => 'app.events' + ], + 'auto_complete' => [ + 'display_property' => 'title', + 'search_properties' => [ + 'title' + ] + ] + ] + ], + 'single_location_selection' => [ + 'default_type' => 'list_overlay', + 'resource_key' => 'locations', + 'types' => [ + 'list_overlay' => [ + 'adapter' => 'table', + 'list_key' => 'locations', + 'display_properties' => [ + 'name' + ], + 'icon' => 'fa-home', + 'empty_text' => 'app.location.no_selections', + 'overlay_title' => 'app.locations' + ], + 'auto_complete' => [ + 'display_property' => 'name', + 'search_properties' => [ + 'name' + ] + ] + ] + ], + ] + ], + ] + ); + } + +// $container->prependExtensionConfig( +// 'sulu_event', +// ['templates' => ['view' => 'event/index.html.twig']] +// ); + + $container->loadFromExtension('framework', [ + 'default_locale' => 'en', + 'translator' => ['paths' => [__DIR__ . '/../Resources/config/translations/']], + // ... + ]); + } +} diff --git a/src/Entity/Event.php b/src/Entity/Event.php new file mode 100644 index 0000000..2eae43b --- /dev/null +++ b/src/Entity/Event.php @@ -0,0 +1,366 @@ + + * + * @ORM\OneToMany(targetEntity="Manuxi\SuluEventBundle\Entity\EventTranslation", mappedBy="event", cascade={"ALL"}, indexBy="locale") + */ + private $translations; + + private $locale = 'en'; + + private $ext = []; + + /** + * @ORM\ManyToOne(targetEntity="Sulu\Bundle\MediaBundle\Entity\MediaInterface") + * + * @Serializer\Exclude + */ + private $image = null; + + public function __construct() + { + $this->enabled = false; + $this->translations = new ArrayCollection(); + $this->initExt(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function isEnabled(): bool + { + return $this->enabled; + } + + public function setEnabled(bool $enabled): self + { + $this->enabled = $enabled; + + return $this; + } + + public function getStartDate(): ?\DateTimeImmutable + { + return $this->startDate; + } + + public function setStartDate(?\DateTimeImmutable $startDate): self + { + $this->startDate = $startDate; + + return $this; + } + + public function getEndDate(): ?\DateTimeImmutable + { + return $this->endDate; + } + + public function setEndDate(?\DateTimeImmutable $endDate): self + { + $this->endDate = $endDate; + + return $this; + } + + public function getLocation(): ?Location + { + return $this->location; + } + + public function setLocation(?Location $location): self + { + $this->location = $location; + + return $this; + } + + /** + * @Serializer\VirtualProperty + */ + public function getLocationId(): ?int + { + if (!$this->location) { + return null; + } + + return $this->location->getId(); + } + + public function getImage(): ?MediaInterface + { + return $this->image; + } + + /** + * @return array|null + * + * @Serializer\VirtualProperty + * @Serializer\SerializedName("image") + */ + public function getImageData(): ?array + { + if (!$this->image) { + return null; + } + + return [ + 'id' => $this->image->getId(), + ]; + } + + public function setImage(?MediaInterface $image): self + { + $this->image = $image; + + return $this; + } + + /** + * @Serializer\VirtualProperty(name="created") + */ + public function getCreated(): ?\DateTime + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getCreated(); + } + + /** + * @Serializer\VirtualProperty(name="title") + */ + public function getTitle(): ?string + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getTitle(); + } + + public function setTitle(string $title): self + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + $translation = $this->createTranslation($this->locale); + } + + $translation->setTitle($title); + + return $this; + } + + /** + * @Serializer\VirtualProperty(name="teaser") + */ + public function getTeaser(): ?string + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getTeaser(); + } + + public function setTeaser(string $teaser): self + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + $translation = $this->createTranslation($this->locale); + } + + $translation->setTeaser($teaser); + + return $this; + } + + /** + * @Serializer\VirtualProperty(name="description") + */ + public function getDescription(): ?string + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getDescription(); + } + + public function setDescription(string $description): self + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + $translation = $this->createTranslation($this->locale); + } + + $translation->setDescription($description); + + return $this; + } + + public function getEventSeo(): EventSeo + { + if (!$this->eventSeo instanceof EventSeo) { + $this->eventSeo = new EventSeo(); + $this->eventSeo->setEvent($this); + } + + return $this->eventSeo; + } + + public function setEventSeo(?EventSeo $eventSeo): self + { + $this->eventSeo = $eventSeo; + + return $this; + } + + public function getExt(): array + { + return $this->ext; + } + + public function setExt(array $ext): self + { + $this->ext = $ext; + + return $this; + } + + public function addExt(string $key, $value): self + { + $this->ext[$key] = $value; + + return $this; + } + + public function hasExt(string $key): bool + { + return \array_key_exists($key, $this->ext); + } + + public function getLocale(): string + { + return $this->locale; + } + + public function setLocale(string $locale): self + { + $this->locale = $locale; + $this->propagateLocale($locale); + + return $this; + } + + /** + * @return EventTranslation[] + */ + public function getTranslations(): array + { + return $this->translations->toArray(); + } + + protected function getTranslation(string $locale): ?EventTranslation + { + if (!$this->translations->containsKey($locale)) { + return null; + } + + return $this->translations->get($locale); + } + + protected function createTranslation(string $locale): EventTranslation + { + $translation = new EventTranslation($this, $locale); + $this->translations->set($locale, $translation); + + return $translation; + } + + private function propagateLocale(string $locale): self + { + $eventSeo = $this->getEventSeo(); + $eventSeo->setLocale($locale); + $this->initExt(); + + return $this; + } + + private function initExt(): self + { + if (!$this->hasExt('seo')) { + $this->addExt('seo', $this->getEventSeo()); + } + + return $this; + } +} diff --git a/src/Entity/EventSeo.php b/src/Entity/EventSeo.php new file mode 100644 index 0000000..b3cd613 --- /dev/null +++ b/src/Entity/EventSeo.php @@ -0,0 +1,255 @@ + + * + * @ORM\OneToMany(targetEntity="EventSeoTranslation", mappedBy="eventSeo", cascade={"ALL"}, indexBy="locale") + * + * @Serializer\Exclude + */ + private $translations; + + private $locale = 'en'; + + public function __construct() + { + $this->translations = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function setId(?int $id): self + { + $this->id = $id; + return $this; + } + + public function getEvent(): ?Event + { + return $this->event; + } + + public function setEvent(Event $event): self + { + $this->event = $event; + return $this; + } + + public function getHideInSitemap(): bool + { + return $this->hideInSitemap; + } + + public function setHideInSitemap(bool $hideInSitemap): self + { + $this->hideInSitemap = $hideInSitemap; + return $this; + } + + public function getNoFollow(): bool + { + return $this->noFollow; + } + + public function setNoFollow(bool $noFollow): self + { + $this->noFollow = $noFollow; + return $this; + } + + public function getNoIndex(): bool + { + return $this->noIndex; + } + + public function setNoIndex(bool $noIndex): self + { + $this->noIndex = $noIndex; + return $this; + } + + /** + * @Serializer\VirtualProperty(name="title") + */ + public function getTitle(): ?string + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getTitle(); + } + + public function setTitle(?string $title): self + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + $translation = $this->createTranslation($this->locale); + } + + $translation->setTitle($title); + + return $this; + } + + /** + * @Serializer\VirtualProperty(name="keywords") + */ + public function getKeywords(): ?string + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getKeywords(); + } + + public function setKeywords(?string $keywords): self + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + $translation = $this->createTranslation($this->locale); + } + + $translation->setKeywords($keywords); + + return $this; + } + + /** + * @Serializer\VirtualProperty(name="canonicalUrl") + */ + public function getCanonicalUrl(): ?string + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getCanonicalUrl(); + } + + public function setCanonicalUrl(?string $canonicalUrl): self + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + $translation = $this->createTranslation($this->locale); + } + + $translation->setCanonicalUrl($canonicalUrl); + + return $this; + } + + /** + * @Serializer\VirtualProperty(name="description") + */ + public function getDescription(): ?string + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + return null; + } + + return $translation->getDescription(); + } + + public function setDescription(?string $description): self + { + $translation = $this->getTranslation($this->locale); + if (!$translation) { + $translation = $this->createTranslation($this->locale); + } + + $translation->setDescription($description); + + return $this; + } + + public function getLocale(): string + { + return $this->locale; + } + + public function setLocale(string $locale): self + { + $this->locale = $locale; + + return $this; + } + + /** + * @return EventTranslation[] + */ + public function getTranslations(): array + { + return $this->translations->toArray(); + } + + protected function getTranslation(string $locale): ?EventSeoTranslation + { + if (!$this->translations->containsKey($locale)) { + return null; + } + + return $this->translations->get($locale); + } + + protected function createTranslation(string $locale): EventSeoTranslation + { + $translation = new EventSeoTranslation($this, $locale); + $this->translations->set($locale, $translation); + + return $translation; + } +} diff --git a/src/Entity/EventSeoTranslation.php b/src/Entity/EventSeoTranslation.php new file mode 100644 index 0000000..0396a40 --- /dev/null +++ b/src/Entity/EventSeoTranslation.php @@ -0,0 +1,122 @@ +eventSeo = $eventSeo; + $this->locale = $locale; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getEventSeo(): EventSeo + { + return $this->eventSeo; + } + + public function getLocale(): string + { + return $this->locale; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(?string $title): self + { + $this->title = $title; + + return $this; + } + + public function getCanonicalUrl(): ?string + { + return $this->canonicalUrl; + } + + public function setCanonicalUrl(?string $canonicalUrl): self + { + $this->canonicalUrl = $canonicalUrl; + + return $this; + } + + public function getKeywords(): ?string + { + return $this->keywords; + } + + public function setKeywords(?string $keywords): self + { + $this->keywords = $keywords; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): self + { + $this->description = $description; + + return $this; + } +} diff --git a/src/Entity/EventTranslation.php b/src/Entity/EventTranslation.php new file mode 100644 index 0000000..d2eb14d --- /dev/null +++ b/src/Entity/EventTranslation.php @@ -0,0 +1,109 @@ +event = $event; + $this->locale = $locale; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getEvent(): Event + { + return $this->event; + } + + public function getLocale(): string + { + return $this->locale; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(?string $title): self + { + $this->title = $title; + + return $this; + } + + public function getTeaser(): ?string + { + return $this->teaser; + } + + public function setTeaser(?string $teaser): self + { + $this->teaser = $teaser; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): self + { + $this->description = $description; + + return $this; + } +} diff --git a/src/Entity/Location.php b/src/Entity/Location.php new file mode 100644 index 0000000..64ba641 --- /dev/null +++ b/src/Entity/Location.php @@ -0,0 +1,130 @@ +id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getStreet(): ?string + { + return $this->street; + } + + public function setStreet(?string $street): self + { + $this->street = $street; + + return $this; + } + + public function getNumber(): ?string + { + return $this->number; + } + + public function setNumber(?string $number): self + { + $this->number = $number; + + return $this; + } + + public function getPostalCode(): ?string + { + return $this->postalCode; + } + + public function setPostalCode(?string $postalCode): self + { + $this->postalCode = $postalCode; + + return $this; + } + + public function getCity(): ?string + { + return $this->city; + } + + public function setCity(?string $city): self + { + $this->city = $city; + + return $this; + } + + public function getCountryCode(): ?string + { + return $this->countryCode; + } + + public function setCountryCode(?string $countryCode): self + { + $this->countryCode = $countryCode; + + return $this; + } +} diff --git a/src/Repository/EventRepository.php b/src/Repository/EventRepository.php new file mode 100644 index 0000000..6571e80 --- /dev/null +++ b/src/Repository/EventRepository.php @@ -0,0 +1,180 @@ + + */ +class EventRepository extends ServiceEntityRepository implements DataProviderRepositoryInterface +{ + use DataProviderRepositoryTrait; + + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Event::class); + } + + public function create(string $locale): Event + { + $event = new Event(); + $event->setLocale($locale); + + return $event; + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(int $id): void + { + /** @var object $event */ + $event = $this->getEntityManager()->getReference( + $this->getClassName(), + $id + ); + + $this->getEntityManager()->remove($event); + $this->getEntityManager()->flush(); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function save(Event $event): void + { + $this->getEntityManager()->persist($event); + $this->getEntityManager()->flush(); + } + + public function findById(int $id, string $locale): ?Event + { + $event = $this->find($id); + + if (!$event) { + return null; + } + + $event->setLocale($locale); + + return $event; + } + + /** + * Returns filtered entities. + * When pagination is active the result count is pageSize + 1 to determine has next page. + * + * @param array $filters array of filters: tags, tagOperator + * @param int $page + * @param int $pageSize + * @param int $limit + * @param string $locale + * @param mixed[] $options + * + * @return object[] + * @noinspection PhpMissingReturnTypeInspection + * @noinspection PhpMissingParamTypeInspection + */ + public function findByFilters($filters, $page, $pageSize, $limit, $locale, $options = []) + { + $entities = $this->parentFindByFilters($filters, $page, $pageSize, $limit, $locale, $options); + + return \array_map( + function (Event $entity) use ($locale) { + return $entity->setLocale($locale); + }, + $entities + ); + } + + protected function appendJoins(QueryBuilder $queryBuilder, string $alias, string $locale): void + { + $queryBuilder->innerJoin($alias . '.translations', 'translation', Join::WITH, 'translation.locale = :locale'); + $queryBuilder->setParameter('locale', $locale); + + $queryBuilder->andWhere($alias . '.enabled = true'); + } + + /** + * Copied temporarily from DataProviderRepositoryTrait since the following code threw + * ReflectionException 'The parameter specified by its name could not be found': + * use DataProviderRepositoryTrait { + * findByFilters as parentFindByFilters; + * } + * + * @param array $filters array of filters: tags, tagOperator + * @param int $page + * @param int $pageSize + * @param int $limit + * @param string $locale + * @param mixed[] $options + * @param UserInterface|null $user + * @param null $entityClass + * @param null $entityAlias + * @param null $permission + * @return object[] + */ + public function parentFindByFilters( + $filters, + $page, + $pageSize, + $limit, + $locale, + $options = [], + ?UserInterface $user = null, + $entityClass = null, + $entityAlias = null, + $permission = null + ) { + $alias = 'entity'; + $queryBuilder = $this->createQueryBuilder($alias) + ->addSelect($alias) + ->where($alias . '.id IN (:ids)') + ->orderBy($alias . '.id', 'ASC'); + $this->appendJoins($queryBuilder, $alias, $locale); + + if (isset($filters['sortBy'])) { + $sortMethod = $filters['sortMethod'] ?? 'asc'; + $sortBy = false !== \strpos($filters['sortBy'], '.') ? $filters['sortBy'] : $alias . '.' . $filters['sortBy']; + + $this->appendSortBy($sortBy, $sortMethod, $queryBuilder, $alias, $locale); + } + + $query = $queryBuilder->getQuery(); + $ids = $this->findByFiltersIds( + $filters, + $page, + $pageSize, + $limit, + $locale, + $options, + $user, + $entityClass, + $entityAlias, + $permission + ); + $query->setParameter('ids', $ids); + + return $query->getResult(); + } +} diff --git a/src/Repository/EventSeoRepository.php b/src/Repository/EventSeoRepository.php new file mode 100644 index 0000000..c25cf06 --- /dev/null +++ b/src/Repository/EventSeoRepository.php @@ -0,0 +1,98 @@ + + */ +class EventSeoRepository extends ServiceEntityRepository +{ +// use DataProviderRepositoryTrait { +// findByFilters as parentFindByFilters; +// } + + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, EventSeo::class); + } + + public function create(string $locale): EventSeo + { + $eventSeo = new EventSeo(); + $eventSeo->setLocale($locale); + + return $eventSeo; + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(int $id): void + { + /** @var object $eventSeo */ + $eventSeo = $this->getEntityManager()->getReference( + $this->getClassName(), + $id + ); + + $this->getEntityManager()->remove($eventSeo); + $this->getEntityManager()->flush(); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function save(EventSeo $eventSeo): void + { + $this->getEntityManager()->persist($eventSeo); + $this->getEntityManager()->flush(); + } + + public function findById(int $id, string $locale): ?EventSeo + { + $eventSeo = $this->find($id); + if (!$eventSeo) { + return null; + } + + $eventSeo->setLocale($locale); + + return $eventSeo; + } + +// /** +// * @param mixed[] $filters +// */ +// public function findByFilters($filters, $page, $pageSize, $limit, $locale, $options = []) +// { +// $entities = $this->parentFindByFilters($filters, $page, $pageSize, $limit, $locale, $options); +// +// return array_map( +// function (EventSeo $entity) use ($locale) { +// return $entity->setLocale($locale); +// }, +// $entities +// ); +// } +// +// protected function appendJoins(QueryBuilder $queryBuilder, string $alias, string $locale): void +// { +// $queryBuilder->innerJoin($alias . '.translations', 'translation', Join::WITH, 'translation.locale = :locale'); +// $queryBuilder->setParameter('locale', $locale); +// } +} diff --git a/src/Repository/EventSeoTranslationRepository.php b/src/Repository/EventSeoTranslationRepository.php new file mode 100644 index 0000000..25abb5b --- /dev/null +++ b/src/Repository/EventSeoTranslationRepository.php @@ -0,0 +1,25 @@ + + */ +class EventSeoTranslationRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, EventSeoTranslation::class); + } +} diff --git a/src/Repository/EventTranslationRepository.php b/src/Repository/EventTranslationRepository.php new file mode 100644 index 0000000..2d5163d --- /dev/null +++ b/src/Repository/EventTranslationRepository.php @@ -0,0 +1,25 @@ + + */ +class EventTranslationRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, EventTranslation::class); + } +} diff --git a/src/Repository/LocationRepository.php b/src/Repository/LocationRepository.php new file mode 100644 index 0000000..3381959 --- /dev/null +++ b/src/Repository/LocationRepository.php @@ -0,0 +1,93 @@ +getEntityManager()->getReference( + $this->getClassName(), + $id + ); + + $this->getEntityManager()->remove($location); + $this->getEntityManager()->flush(); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function save(Location $location): void + { + $this->getEntityManager()->persist($location); + $this->getEntityManager()->flush(); + } + + public function findById(int $id): ?Location + { + $location = $this->find($id); + if (!$location) { + return null; + } + + return $location; + } + + // /** + // * @return Location[] Returns an array of Location objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('l.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Location + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Resources/config/controller.xml b/src/Resources/config/controller.xml new file mode 100644 index 0000000..2d6c0dd --- /dev/null +++ b/src/Resources/config/controller.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/forms/event_details.xml b/src/Resources/config/forms/event_details.xml new file mode 100644 index 0000000..2c97753 --- /dev/null +++ b/src/Resources/config/forms/event_details.xml @@ -0,0 +1,58 @@ + +
+ event_details + + + + + sulu_admin.title + + + + + + + + + app.image + + + + + + + + + app.location + + + + + + app.teaser + + + + + + app.start_date + + + + + + app.end_date + + + + + + sulu_admin.description + + + + +
\ No newline at end of file diff --git a/src/Resources/config/forms/location_details.xml b/src/Resources/config/forms/location_details.xml new file mode 100644 index 0000000..b688374 --- /dev/null +++ b/src/Resources/config/forms/location_details.xml @@ -0,0 +1,53 @@ + +
+ location_details + + + + + sulu_admin.name + + + + + + sulu_contact.street + + + + + + sulu_contact.number + + + + + + sulu_contact.zip + + + + + + sulu_contact.city + + + + + + sulu_contact.country + + + + + + + +
diff --git a/src/Resources/config/lists/events.xml b/src/Resources/config/lists/events.xml new file mode 100644 index 0000000..a22895f --- /dev/null +++ b/src/Resources/config/lists/events.xml @@ -0,0 +1,107 @@ + + + events + + + + Manuxi\SuluEventBundle\Entity\EventTranslation + Manuxi\SuluEventBundle\Entity\Event.translations + Manuxi\SuluEventBundle\Entity\EventTranslation.locale = :locale + + + + + + %sulu.model.user.class%_changer + Manuxi\SuluEventBundle\Entity\EventTranslation.changer + + + %sulu.model.contact.class%_changer + %sulu.model.user.class%_changer.contact + + + + + + %sulu.model.user.class%_creator + Manuxi\SuluEventBundle\Entity\EventTranslation.creator + + + %sulu.model.contact.class%_creator + %sulu.model.user.class%_creator.contact + + + + + + id + Manuxi\SuluEventBundle\Entity\Event + + + + title + Manuxi\SuluEventBundle\Entity\EventTranslation + + + + + + enabled + Manuxi\SuluEventBundle\Entity\Event + + + + startDate + Manuxi\SuluEventBundle\Entity\Event + + + + endDate + Manuxi\SuluEventBundle\Entity\Event + + + + created + Manuxi\SuluEventBundle\Entity\EventTranslation + + + + + + + firstName + %sulu.model.contact.class%_creator + + + + + lastName + %sulu.model.contact.class%_creator + + + + + + + changed + Manuxi\SuluEventBundle\Entity\EventTranslation + + + + + + + firstName + %sulu.model.contact.class%_changer + + + + + lastName + %sulu.model.contact.class%_changer + + + + + + diff --git a/src/Resources/config/lists/locations.xml b/src/Resources/config/lists/locations.xml new file mode 100644 index 0000000..cb0feae --- /dev/null +++ b/src/Resources/config/lists/locations.xml @@ -0,0 +1,41 @@ + + + locations + + + + id + Manuxi\SuluEventBundle\Entity\Location + + + + name + Manuxi\SuluEventBundle\Entity\Location + + + + street + Manuxi\SuluEventBundle\Entity\Location + + + + number + Manuxi\SuluEventBundle\Entity\Location + + + + postalCode + Manuxi\SuluEventBundle\Entity\Location + + + + city + Manuxi\SuluEventBundle\Entity\Location + + + + countryCode + Manuxi\SuluEventBundle\Entity\Location + + + diff --git a/src/Resources/config/routes.yml b/src/Resources/config/routes.yml new file mode 100644 index 0000000..bcf8dc4 --- /dev/null +++ b/src/Resources/config/routes.yml @@ -0,0 +1,17 @@ +# +# +# +# +# /veranstaltungen/{id}/{slug} +# /events/{id}/{slug} +# +# +# +event: + path: + en: /events/{id}/{slug} + de: /veranstaltungen/{id}/{slug} + controller: Manuxi\SuluEventBundle\Controller\Website\EventController::indexAction diff --git a/src/Resources/config/routes_admin.yml b/src/Resources/config/routes_admin.yml new file mode 100644 index 0000000..b343d93 --- /dev/null +++ b/src/Resources/config/routes_admin.yml @@ -0,0 +1,14 @@ +app_events_api: + type: rest + prefix: /admin/api + resource: Manuxi\SuluEventBundle\Controller\Admin\EventController + name_prefix: app. + options: + expose: true +app_locations_api: + type: rest + resource: Manuxi\SuluEventBundle\Controller\Admin\LocationController + prefix: /admin/api + name_prefix: app. + options: + expose: true diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml new file mode 100644 index 0000000..c357006 --- /dev/null +++ b/src/Resources/config/services.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/translations/admin.de.yml b/src/Resources/config/translations/admin.de.yml new file mode 100644 index 0000000..c4f2012 --- /dev/null +++ b/src/Resources/config/translations/admin.de.yml @@ -0,0 +1,19 @@ +app: + id: Id + name: Name + description: Beschreibung + start_date: Start + end_date: Ende + event: Veranstaltung + events: Veranstaltungen + event_selection_label: "{count} {count, plural, =1 {Event} other {Events}} ausgewählt" + select_events: "Events auswählen" + no_event_selected: "Kein Event ausgewählt" + select_event: "Event auswählen" + teaser: Teaser + image: Bild + enabled: freigegeben + enable_event: Veranstaltung freigeben + location: "Standort" + locations: "Standorte" + location.no_selections: "Kein Standort ausgewählt" diff --git a/src/Resources/config/translations/admin.en.yml b/src/Resources/config/translations/admin.en.yml new file mode 100644 index 0000000..47815b7 --- /dev/null +++ b/src/Resources/config/translations/admin.en.yml @@ -0,0 +1,19 @@ +app: + id: Id + name: Name + description: Description + start_date: Start + end_date: End + event: Event + events: Events + event_selection_label: "{count} {count, plural, =1 {event} other {events}} selected" + select_events: "Select events" + no_event_selected: "No event selected" + select_event: "Select event" + teaser: Teaser + image: Image + enabled: enabled + enable_event: Enable event + location: "Location" + locations: "Locations" + location.no_selections: "No location selected" \ No newline at end of file diff --git a/src/Service/CountryCodeSelect.php b/src/Service/CountryCodeSelect.php new file mode 100644 index 0000000..ede42aa --- /dev/null +++ b/src/Service/CountryCodeSelect.php @@ -0,0 +1,27 @@ + $title) { + $values[] = [ + 'name' => $code, + 'title' => $title, + ]; + } + + return $values; + } +} diff --git a/src/SuluEventBundle.php b/src/SuluEventBundle.php new file mode 100644 index 0000000..ae76117 --- /dev/null +++ b/src/SuluEventBundle.php @@ -0,0 +1,10 @@ +