diff --git a/TODO_mongo_odm.md b/TODO_mongo_odm.md new file mode 100644 index 00000000..e62ff089 --- /dev/null +++ b/TODO_mongo_odm.md @@ -0,0 +1,12 @@ +# Utiliser les functions Twig object + +# Menu entries (entry type = document) + +# Passer les configPass en mongo_odm + - EmbeddedListViewConfigPass: OK + - ExcludeFieldsConfigPass + - ListFormFiltersConfigPass OK + - ShortFormTypeConfigPass: OK + - ShowViewConfigPass: ? + +# Tests ! diff --git a/composer.json b/composer.json index 02d1bf6d..790ee1f6 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,10 @@ "twig/twig": "^2.4" }, "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "alterphp/easyadmin-mongo-odm-bundle": "dev-master", "doctrine/doctrine-fixtures-bundle": "^3.0", + "doctrine/mongodb-odm-bundle": "^3.4", "friendsofphp/php-cs-fixer": "^2.11", "php-coveralls/php-coveralls": "^2.0", "phpstan/phpstan": "^0.9.2", @@ -49,7 +52,11 @@ "symfony/var-dumper": "^4.1" }, "config": { - "sort-packages": true + "sort-packages": true, + "platform": { + "ext-mongodb": "1.3.3", + "ext-mongo": "1.6.16" + } }, "autoload": { "psr-4": { "AlterPHP\\EasyAdminExtensionBundle\\": "src/" }, diff --git a/docker-compose.yml.dist b/docker-compose.yml.dist index 7430aa76..11169b53 100644 --- a/docker-compose.yml.dist +++ b/docker-compose.yml.dist @@ -14,8 +14,7 @@ services: volumes: - ./:/app entrypoint: - - php - - /app/vendor/phpunit/phpunit/phpunit + - ./vendor/bin/phpunit php-cs-fixer: image: ekreative/php-cs-fixer diff --git a/docker/phpunit/Dockerfile b/docker/phpunit/Dockerfile index bc5da805..23e463ef 100644 --- a/docker/phpunit/Dockerfile +++ b/docker/phpunit/Dockerfile @@ -1,4 +1,10 @@ -FROM phpunit/phpunit:latest +FROM php:7.2-cli -RUN apk --no-cache add php7-iconv -RUN apk --no-cache add php7-simplexml +RUN docker-php-ext-install -j$(nproc) mysqli + +# Install MONGO +RUN pecl install mongodb \ + && docker-php-ext-enable mongodb + +VOLUME ["/app"] +WORKDIR /app diff --git a/src/Configuration/EmbeddedListViewConfigPass.php b/src/Configuration/EmbeddedListViewConfigPass.php index a03a113a..8181afc8 100644 --- a/src/Configuration/EmbeddedListViewConfigPass.php +++ b/src/Configuration/EmbeddedListViewConfigPass.php @@ -5,8 +5,8 @@ use EasyCorp\Bundle\EasyAdminBundle\Configuration\ConfigPassInterface; /** - * Initializes the configuration for all the views of each entity, which is - * needed when some entity relies on the default configuration for some view. + * Initializes the configuration for all the views of each object of type "%s", which is + * needed when some object of type "%s" relies on the default configuration for some view. */ class EmbeddedListViewConfigPass implements ConfigPassInterface { @@ -32,9 +32,13 @@ public function process(array $backendConfig) */ private function processOpenNewTabConfig(array $backendConfig) { - foreach ($backendConfig['entities'] as $entityName => $entityConfig) { - if (!isset($entityConfig['embeddedList']['open_new_tab'])) { - $backendConfig['entities'][$entityName]['embeddedList']['open_new_tab'] = $this->defaultOpenNewTab; + foreach (['entities', 'documents'] as $objectTypeRootKey) { + if (isset($backendConfig[$objectTypeRootKey]) && \is_array($backendConfig[$objectTypeRootKey])) { + foreach ($backendConfig[$objectTypeRootKey] as $objectName => $objectConfig) { + if (!isset($objectConfig['embeddedList']['open_new_tab'])) { + $backendConfig[$objectTypeRootKey][$objectName]['embeddedList']['open_new_tab'] = $this->defaultOpenNewTab; + } + } } } @@ -48,25 +52,29 @@ private function processOpenNewTabConfig(array $backendConfig) */ private function processSortingConfig(array $backendConfig) { - foreach ($backendConfig['entities'] as $entityName => $entityConfig) { - if ( - !isset($entityConfig['embeddedList']['sort']) - && isset($entityConfig['list']['sort']) - ) { - $backendConfig['entities'][$entityName]['embeddedList']['sort'] = $entityConfig['list']['sort']; - } elseif (isset($entityConfig['embeddedList']['sort'])) { - $sortConfig = $entityConfig['embeddedList']['sort']; - if (!\is_string($sortConfig) && !\is_array($sortConfig)) { - throw new \InvalidArgumentException(\sprintf('The "sort" option of the "embeddedList" view of the "%s" entity contains an invalid value (it can only be a string or an array).', $entityName)); - } + foreach (['entities', 'documents'] as $objectTypeRootKey) { + if (isset($backendConfig[$objectTypeRootKey]) && \is_array($backendConfig[$objectTypeRootKey])) { + foreach ($backendConfig[$objectTypeRootKey] as $objectName => $objectConfig) { + if ( + !isset($objectConfig['embeddedList']['sort']) + && isset($objectConfig['list']['sort']) + ) { + $backendConfig[$objectTypeRootKey][$objectName]['embeddedList']['sort'] = $objectConfig['list']['sort']; + } elseif (isset($objectConfig['embeddedList']['sort'])) { + $sortConfig = $objectConfig['embeddedList']['sort']; + if (!\is_string($sortConfig) && !\is_array($sortConfig)) { + throw new \InvalidArgumentException(\sprintf('The "sort" option of the "embeddedList" view of the "%s" object contains an invalid value (it can only be a string or an array).', $objectName)); + } - if (\is_string($sortConfig)) { - $sortConfig = ['field' => $sortConfig, 'direction' => 'DESC']; - } else { - $sortConfig = ['field' => $sortConfig[0], 'direction' => \strtoupper($sortConfig[1])]; - } + if (\is_string($sortConfig)) { + $sortConfig = ['field' => $sortConfig, 'direction' => 'DESC']; + } else { + $sortConfig = ['field' => $sortConfig[0], 'direction' => \strtoupper($sortConfig[1])]; + } - $backendConfig['entities'][$entityName]['embeddedList']['sort'] = $sortConfig; + $backendConfig[$objectTypeRootKey][$objectName]['embeddedList']['sort'] = $sortConfig; + } + } } } diff --git a/src/Configuration/ListFormFiltersConfigPass.php b/src/Configuration/ListFormFiltersConfigPass.php index 35a7ce1a..27c25526 100644 --- a/src/Configuration/ListFormFiltersConfigPass.php +++ b/src/Configuration/ListFormFiltersConfigPass.php @@ -32,24 +32,34 @@ public function __construct(ManagerRegistry $doctrine) */ public function process(array $backendConfig): array { - if (!isset($backendConfig['entities'])) { - return $backendConfig; + if (isset($backendConfig['entities']) && \is_array($backendConfig['entities'])) { + $this->processObjectListFormFilters('entity', $backendConfig['entities']); } - foreach ($backendConfig['entities'] as $entityName => $entityConfig) { - if (!isset($entityConfig['list']['form_filters'])) { + if (isset($backendConfig['documents']) && \is_array($backendConfig['documents'])) { + $this->processObjectListFormFilters('document', $backendConfig['documents']); + } + + return $backendConfig; + } + + private function processObjectListFormFilters(string $objectType, array &$objectConfigs) + { + foreach ($objectConfigs as $objectName => $objectConfig) { + if (!isset($objectConfig['list']['form_filters'])) { continue; } $formFilters = []; - foreach ($entityConfig['list']['form_filters'] as $i => $formFilter) { + foreach ($objectConfig['list']['form_filters'] as $key => $formFilter) { // Detects invalid config node if (!\is_string($formFilter) && !\is_array($formFilter)) { throw new \RuntimeException( \sprintf( - 'The values of the "form_filters" option for the list view of the "%s" entity can only be strings or arrays.', - $entityConfig['class'] + 'The values of the "form_filters" option for the list view of the "%s" object of type "%s" can only be strings or arrays.', + $objectConfig['class'], + $objectType ) ); } @@ -59,12 +69,17 @@ public function process(array $backendConfig): array $filterConfig = ['property' => $formFilter]; } else { if (!\array_key_exists('property', $formFilter)) { - throw new \RuntimeException( - \sprintf( - 'One of the values of the "form_filters" option for the "list" view of the "%s" entity does not define the mandatory option "property".', - $entityConfig['class'] - ) - ); + if (\is_string($key)) { + $formFilter['property'] = $key; + } else { + throw new \RuntimeException( + \sprintf( + 'One of the values of the "form_filters" option for the "list" view of the "%s" object of type "%s" does not define the mandatory option "property".', + $objectConfig['class'], + $objectType + ) + ); + } } $filterConfig = $formFilter; @@ -75,11 +90,13 @@ public function process(array $backendConfig): array // Auto set label with name value $filterConfig['label'] = $filterConfig['label'] ?? $filterConfig['name']; - $this->configureFilter( - $entityConfig['class'], - $filterConfig, - $backendConfig['translation_domain'] ?? 'EasyAdminBundle' - ); + if ('entity' === $objectType) { + $this->configureEntityFormFilter( + $objectConfig['class'], + $filterConfig, + $backendConfig['translation_domain'] ?? 'EasyAdminBundle' + ); + } // If type is not configured at this steps => not guessable if (!isset($filterConfig['type'])) { @@ -90,13 +107,11 @@ public function process(array $backendConfig): array } // set form filters config and form ! - $backendConfig['entities'][$entityName]['list']['form_filters'] = $formFilters; + $objectConfigs[$objectName]['list']['form_filters'] = $formFilters; } - - return $backendConfig; } - private function configureFilter(string $entityClass, array &$filterConfig, string $translationDomain) + private function configureEntityFormFilter(string $entityClass, array &$filterConfig, string $translationDomain) { $em = $this->doctrine->getManagerForClass($entityClass); $entityMetadata = $em->getMetadataFactory()->getMetadataFor($entityClass); @@ -110,17 +125,17 @@ private function configureFilter(string $entityClass, array &$filterConfig, stri } if ($entityMetadata->hasField($filterConfig['property'])) { - $this->configureFieldFilter( + $this->configureEntityPropertyFilter( $entityClass, $entityMetadata->getFieldMapping($filterConfig['property']), $filterConfig, $translationDomain ); } elseif ($entityMetadata->hasAssociation($filterConfig['property'])) { - $this->configureAssociationFilter( + $this->configureEntityAssociationFilter( $entityClass, $entityMetadata->getAssociationMapping($filterConfig['property']), $filterConfig ); } } - private function configureFieldFilter( + private function configureEntityPropertyFilter( string $entityClass, array $fieldMapping, array &$filterConfig, string $translationDomain ) { switch ($fieldMapping['type']) { @@ -181,7 +196,7 @@ private function configureFieldFilter( } } - private function configureAssociationFilter(string $entityClass, array $associationMapping, array &$filterConfig) + private function configureEntityAssociationFilter(string $entityClass, array $associationMapping, array &$filterConfig) { // To-One (EasyAdminAutocompleteType) if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { diff --git a/src/Configuration/ShortFormTypeConfigPass.php b/src/Configuration/ShortFormTypeConfigPass.php index 2af9e79f..06e058b7 100644 --- a/src/Configuration/ShortFormTypeConfigPass.php +++ b/src/Configuration/ShortFormTypeConfigPass.php @@ -41,15 +41,16 @@ public function process(array $backendConfig) private function replaceShortNameTypes(array $backendConfig) { - if ( - !isset($backendConfig['entities']) - || !\is_array($backendConfig['entities']) - ) { - return $backendConfig; + if (isset($backendConfig['entities']) && \is_array($backendConfig['entities'])) { + foreach ($backendConfig['entities'] as &$entityConfig) { + $entityConfig = $this->replaceShortFormTypesInObjectConfig($entityConfig); + } } - foreach ($backendConfig['entities'] as &$entity) { - $entity = $this->replaceShortFormTypesInObjectConfig($entity); + if (isset($backendConfig['documents']) && \is_array($backendConfig['documents'])) { + foreach ($backendConfig['documents'] as &$documentConfig) { + $documentConfig = $this->replaceShortFormTypesInObjectConfig($documentConfig); + } } return $backendConfig; diff --git a/src/Configuration/ShowViewConfigPass.php b/src/Configuration/ShowViewConfigPass.php index 7fb4c8c8..e44c032c 100644 --- a/src/Configuration/ShowViewConfigPass.php +++ b/src/Configuration/ShowViewConfigPass.php @@ -49,21 +49,25 @@ public function process(array $backendConfig) */ private function processCustomShowTypes(array $backendConfig) { - foreach ($backendConfig['entities'] as $entityName => $entityConfig) { - foreach ($entityConfig['show']['fields'] as $fieldName => $fieldMetadata) { - if (\array_key_exists($fieldMetadata['type'], static::$mapTypeToTemplates)) { - $template = $this->isFieldTemplateDefined($fieldMetadata) - ? $fieldMetadata['template'] - : static::$mapTypeToTemplates[$fieldMetadata['type']]; - $entityConfig['show']['fields'][$fieldName]['template'] = $template; - - $entityConfig['show']['fields'][$fieldName]['template_options'] = $this->processTemplateOptions( - $fieldMetadata['type'], $fieldMetadata - ); + foreach (['entities', 'documents'] as $objectTypeRootKey) { + if (isset($backendConfig[$objectTypeRootKey]) && \is_array($backendConfig[$objectTypeRootKey])) { + foreach ($backendConfig[$objectTypeRootKey] as $objectName => $objectConfig) { + foreach ($objectConfig['show']['fields'] as $fieldName => $fieldMetadata) { + if (\array_key_exists($fieldMetadata['type'], static::$mapTypeToTemplates)) { + $template = $this->isFieldTemplateDefined($fieldMetadata) + ? $fieldMetadata['template'] + : static::$mapTypeToTemplates[$fieldMetadata['type']]; + $objectConfig['show']['fields'][$fieldName]['template'] = $template; + + $objectConfig['show']['fields'][$fieldName]['template_options'] = $this->processTemplateOptions( + $fieldMetadata['type'], $fieldMetadata + ); + } + } + + $backendConfig[$objectTypeRootKey][$objectName] = $objectConfig; } } - - $backendConfig['entities'][$entityName] = $entityConfig; } return $backendConfig; @@ -81,19 +85,47 @@ private function processTemplateOptions(string $type, array $fieldMetadata) switch ($type) { case 'embedded_list': - $parentEntityFqcn = $templateOptions['parent_entity_fqcn'] ?? $fieldMetadata['sourceEntity']; - $parentEntityProperty = $templateOptions['parent_entity_property'] ?? $fieldMetadata['property']; - $entityFqcn = $this->embeddedListHelper->getEntityFqcnFromParent( - $parentEntityFqcn, $parentEntityProperty + // Deprecations + if (isset($templateOptions['entity_fqcn']) && !isset($templateOptions['object_fqcn'])) { + $templateOptions['object_fqcn'] = $templateOptions['entity_fqcn']; + unset($templateOptions['entity_fqcn']); + + \trigger_error(\sprintf('The "entity_fqcn" option for embedded_list is deprecated since version 1.4.0 and it will be removed in 2.0. Use the "object_fqcn" option instead.'), E_USER_DEPRECATED); + } + if (isset($templateOptions['parent_entity_fqcn']) && !isset($templateOptions['parent_object_fqcn'])) { + $templateOptions['parent_object_fqcn'] = $templateOptions['parent_entity_fqcn']; + unset($templateOptions['parent_entity_fqcn']); + + \trigger_error(\sprintf('The "parent_entity_fqcn" option for embedded_list is deprecated since version 1.4.0 and it will be removed in 2.0. Use the "parent_object_fqcn" option instead.'), E_USER_DEPRECATED); + } + if (isset($templateOptions['parent_entity_property']) && !isset($templateOptions['parent_object_property'])) { + $templateOptions['parent_object_property'] = $templateOptions['parent_entity_property']; + unset($templateOptions['parent_entity_property']); + + \trigger_error(\sprintf('The "parent_entity_property" option for embedded_list is deprecated since version 1.4.0 and it will be removed in 2.0. Use the "parent_object_property" option instead.'), E_USER_DEPRECATED); + } + + $parentObjectFqcn = $templateOptions['parent_object_fqcn'] ?? $fieldMetadata['sourceEntity']; + $parentObjectProperty = $templateOptions['parent_object_property'] ?? $fieldMetadata['property']; + $objectFqcn = $this->embeddedListHelper->getEntityFqcnFromParent( + $parentObjectFqcn, $parentObjectProperty ); - if (!isset($templateOptions['entity_fqcn'])) { - $templateOptions['entity_fqcn'] = $entityFqcn; + + if (isset($templateOptions['document'])) { + $templateOptions['object_type'] = 'document'; + } else { + $templateOptions['object_type'] = 'entity'; } - if (!isset($templateOptions['parent_entity_property'])) { - $templateOptions['parent_entity_property'] = $parentEntityProperty; + + if (!isset($templateOptions['entity']) && !isset($templateOptions['document'])) { + $templateOptions['entity'] = $this->embeddedListHelper->guessEntityEntry($objectFqcn); + } + + if (!isset($templateOptions['object_fqcn'])) { + $templateOptions['object_fqcn'] = $objectFqcn; } - if (!isset($templateOptions['entity'])) { - $templateOptions['entity'] = $this->embeddedListHelper->guessEntityEntry($entityFqcn); + if (!isset($templateOptions['parent_object_property'])) { + $templateOptions['parent_object_property'] = $parentObjectProperty; } if (!isset($templateOptions['filters'])) { $templateOptions['filters'] = []; diff --git a/src/Controller/EasyAdminController.php b/src/Controller/EasyAdminController.php index 59735004..cd5b2575 100644 --- a/src/Controller/EasyAdminController.php +++ b/src/Controller/EasyAdminController.php @@ -24,6 +24,7 @@ protected function embeddedListAction() $this->dispatch(EasyAdminEvents::POST_LIST, ['paginator' => $paginator]); return $this->render('@EasyAdminExtension/default/embedded_list.html.twig', [ + 'objectType' => 'entity', 'paginator' => $paginator, 'fields' => $fields, 'masterRequest' => $this->get('request_stack')->getMasterRequest(), diff --git a/src/Controller/MongoOdmEasyAdminController.php b/src/Controller/MongoOdmEasyAdminController.php new file mode 100644 index 00000000..2ae12700 --- /dev/null +++ b/src/Controller/MongoOdmEasyAdminController.php @@ -0,0 +1,64 @@ +dispatch(EasyAdminMongoOdmEvents::PRE_LIST); + + $fields = $this->document['list']['fields']; + $paginator = $this->mongoOdmFindAll( + $this->document['class'], + $this->request->query->get('page', 1), + $this->config['list']['max_results'] ?: 25, + $this->request->query->get('sortField'), + $this->request->query->get('sortDirection') + ); + + $this->dispatch(EasyAdminMongoOdmEvents::POST_LIST, ['paginator' => $paginator]); + + return $this->render('@EasyAdminExtension/default/embedded_list.html.twig', [ + 'objectType' => 'document', + 'paginator' => $paginator, + 'fields' => $fields, + 'masterRequest' => $this->get('request_stack')->getMasterRequest(), + ]); + } + + /** + * {@inheritdoc} + * + * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException + */ + protected function isActionAllowed($actionName) + { + switch ($actionName) { + // autocomplete action is mapped to list action for access permissions + case 'autocomplete': + // embeddedList action is mapped to list action for access permissions + case 'embeddedList': + $actionName = 'list'; + break; + default: + break; + } + + // Get item for edit/show or custom actions => security voters may apply + $easyadminMongoOdm = $this->request->attributes->get('easyadmin_mongo_odm'); + $subject = $easyadminMongoOdm['item'] ?? null; + $this->get(AdminAuthorizationChecker::class)->checksUserAccess($this->document, $actionName, $subject); + + return parent::isActionAllowed($actionName); + } +} diff --git a/src/DependencyInjection/Compiler/MongoOdmPass.php b/src/DependencyInjection/Compiler/MongoOdmPass.php new file mode 100644 index 00000000..026e956c --- /dev/null +++ b/src/DependencyInjection/Compiler/MongoOdmPass.php @@ -0,0 +1,22 @@ +getParameter('kernel.bundles')); + $hasEasyAdminMongoOdmBundle = $mongoOdmBundleClassExists && $mongoOdmBundleLoaded; + + // Disable services specific to EasyAdminMongoOdmBundle + if (!$hasEasyAdminMongoOdmBundle) { + $container->removeDefinition('alterphp.easyadmin_extension.subscriber.mongo_odm_post_query_builder'); + } + } +} diff --git a/src/DependencyInjection/Compiler/TwigPathPass.php b/src/DependencyInjection/Compiler/TwigPathPass.php index 9478b0ee..5e6b1a9d 100644 --- a/src/DependencyInjection/Compiler/TwigPathPass.php +++ b/src/DependencyInjection/Compiler/TwigPathPass.php @@ -3,9 +3,11 @@ namespace AlterPHP\EasyAdminExtensionBundle\DependencyInjection\Compiler; use AlterPHP\EasyAdminExtensionBundle\EasyAdminExtensionBundle; +use AlterPHP\EasyAdminMongoOdmBundle\EasyAdminMongoOdmBundle; use EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; class TwigPathPass implements CompilerPassInterface { @@ -15,24 +17,37 @@ public function process(ContainerBuilder $container) $twigLoaderFilesystemDefinition = $container->getDefinition($twigLoaderFilesystemId); // Replaces native EasyAdmin templates + $this->coverNamespace($twigLoaderFilesystemDefinition, 'EasyAdmin', EasyAdminBundle::class); + + // CHECK_MONGO_ODM + $mongoOdmBundleClassExists = \class_exists(EasyAdminMongoOdmBundle::class); + $mongoOdmBundleLoaded = \in_array(EasyAdminMongoOdmBundle::class, $container->getParameter('kernel.bundles')); + $hasEasyAdminMongoOdmBundle = $mongoOdmBundleClassExists && $mongoOdmBundleLoaded; + if ($hasEasyAdminMongoOdmBundle) { + $this->coverNamespace($twigLoaderFilesystemDefinition, 'EasyAdminMongoOdm', EasyAdminMongoOdmBundle::class); + } + } + + private function coverNamespace(Definition $twigLoaderFilesystemDefinition, string $namespace, string $bundleClass) + { $easyAdminExtensionBundleRefl = new \ReflectionClass(EasyAdminExtensionBundle::class); if ($easyAdminExtensionBundleRefl->isUserDefined()) { $easyAdminExtensionBundlePath = \dirname((string) $easyAdminExtensionBundleRefl->getFileName()); $easyAdminExtensionTwigPath = $easyAdminExtensionBundlePath.'/Resources/views'; $twigLoaderFilesystemDefinition->addMethodCall( 'prependPath', - [$easyAdminExtensionTwigPath, 'EasyAdmin'] + [$easyAdminExtensionTwigPath, $namespace] ); } - $nativeEasyAdminBundleRefl = new \ReflectionClass(EasyAdminBundle::class); - if ($nativeEasyAdminBundleRefl->isUserDefined()) { - $nativeEasyAdminBundlePath = \dirname((string) $nativeEasyAdminBundleRefl->getFileName()); - $nativeEasyAdminTwigPath = $nativeEasyAdminBundlePath.'/Resources/views'; + $coveredBundleRefl = new \ReflectionClass($bundleClass); + if ($coveredBundleRefl->isUserDefined()) { + $coveredBundleBundlePath = \dirname((string) $coveredBundleRefl->getFileName()); + $coveredTwigNamespacePath = $coveredBundleBundlePath.'/Resources/views'; // Defines a namespace from native EasyAdmin templates $twigLoaderFilesystemDefinition->addMethodCall( 'addPath', - [$nativeEasyAdminTwigPath, 'BaseEasyAdmin'] + [$coveredTwigNamespacePath, 'Base'.$namespace] ); } } diff --git a/src/EasyAdminExtensionBundle.php b/src/EasyAdminExtensionBundle.php index 21c89a8f..df214598 100644 --- a/src/EasyAdminExtensionBundle.php +++ b/src/EasyAdminExtensionBundle.php @@ -2,6 +2,7 @@ namespace AlterPHP\EasyAdminExtensionBundle; +use AlterPHP\EasyAdminExtensionBundle\DependencyInjection\Compiler\MongoOdmPass; use AlterPHP\EasyAdminExtensionBundle\DependencyInjection\Compiler\TwigPathPass; use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -14,6 +15,9 @@ public function build(ContainerBuilder $container) { parent::build($container); + // Priority 1 to pass just before RegisterListenerPass (in case we have to enable/disable listeners) ! + $container->addCompilerPass(new MongoOdmPass(), PassConfig::TYPE_BEFORE_REMOVING, 1); + $container->addCompilerPass(new TwigPathPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); $this->addRegisterMappingsPass($container); diff --git a/src/EventListener/AbstractPostQueryBuilderSubscriber.php b/src/EventListener/AbstractPostQueryBuilderSubscriber.php new file mode 100644 index 00000000..b8a5ba34 --- /dev/null +++ b/src/EventListener/AbstractPostQueryBuilderSubscriber.php @@ -0,0 +1,141 @@ +listFormFiltersHelper = $listFormFiltersHelper; + } + + /** + * Called on POST_LIST_QUERY_BUILDER event. + * + * @param GenericEvent $event + */ + public function onPostListQueryBuilder(GenericEvent $event) + { + $queryBuilder = $event->getArgument('query_builder'); + + if (!$this->supportsQueryBuilder($queryBuilder)) { + throw new \RuntimeException('Passed queryBuilder is not supported !'); + } + + // Request filters + if ($event->hasArgument('request')) { + $this->applyRequestFilters($queryBuilder, $event->getArgument('request')->get('filters', [])); + } + + // List form filters + if ($event->hasArgument(static::APPLIABLE_OBJECT_TYPE)) { + $objectConfig = $event->getArgument(static::APPLIABLE_OBJECT_TYPE); + if (isset($objectConfig['list']['form_filters'])) { + $listFormFiltersForm = $this->listFormFiltersHelper->getListFormFilters($objectConfig['list']['form_filters']); + if ($listFormFiltersForm->isSubmitted() && $listFormFiltersForm->isValid()) { + $this->applyFormFilters($queryBuilder, $listFormFiltersForm->getData()); + } + } + } + } + + /** + * Called on POST_SEARCH_QUERY_BUILDER event. + * + * @param GenericEvent $event + */ + public function onPostSearchQueryBuilder(GenericEvent $event) + { + $queryBuilder = $event->getArgument('query_builder'); + + if (!$this->supportsQueryBuilder($queryBuilder)) { + throw new \RuntimeException('Passed queryBuilder is not supported !'); + } + + if ($event->hasArgument('request')) { + $this->applyRequestFilters($queryBuilder, $event->getArgument('request')->get('filters', [])); + } + } + + /** + * Applies request filters on queryBuilder. + * + * @param object $queryBuilder + * @param array $filters + */ + protected function applyRequestFilters($queryBuilder, array $filters = []) + { + foreach ($filters as $field => $value) { + // Empty string and numeric keys is considered as "not applied filter" + if ('' === $value || \is_int($field)) { + continue; + } + + $operator = \is_array($value) ? ListFilter::OPERATOR_IN : ListFilter::OPERATOR_EQUALS; + $listFilter = ListFilter::createFromRequest($field, $operator, $value); + + $this->filterQueryBuilder($queryBuilder, $field, $listFilter); + } + } + + /** + * Applies form filters on queryBuilder. + * + * @param object $queryBuilder + * @param array $filters + */ + protected function applyFormFilters($queryBuilder, array $filters = []) + { + foreach ($filters as $field => $listFilter) { + if (null === $listFilter) { + continue; + } + + $this->filterQueryBuilder($queryBuilder, $field, $listFilter); + } + } + + protected function filterEasyadminAutocompleteValue($value) + { + if (!\is_array($value) || !isset($value['autocomplete']) || 1 !== \count($value)) { + return $value; + } + + return $value['autocomplete']; + } + + /** + * Checks if filter is directly appliable on queryBuilder. + * + * @param object $queryBuilder + * @param string $field + * + * @return bool + */ + protected function isFilterAppliable($queryBuilder, string $field): bool + { + return true; + } +} diff --git a/src/EventListener/MongoOdmPostQueryBuilderSubscriber.php b/src/EventListener/MongoOdmPostQueryBuilderSubscriber.php new file mode 100644 index 00000000..fbc1c406 --- /dev/null +++ b/src/EventListener/MongoOdmPostQueryBuilderSubscriber.php @@ -0,0 +1,100 @@ + ['onPostListQueryBuilder'], + EasyAdminMongoOdmEvents::POST_SEARCH_QUERY_BUILDER => ['onPostSearchQueryBuilder'], + ]; + } + + /** + * {@inheritdoc} + */ + protected function supportsQueryBuilder($queryBuilder): bool + { + return $queryBuilder instanceof QueryBuilder; + } + + /** + * Filters queryBuilder. + * + * @param QueryBuilder $queryBuilder + * @param string $field + * @param ListFilter $listFilter + */ + protected function filterQueryBuilder(QueryBuilder $queryBuilder, string $field, ListFilter $listFilter) + { + $value = $this->filterEasyadminAutocompleteValue($listFilter->getValue()); + // Empty string and numeric keys is considered as "not applied filter" + if (null === $value || '' === $value || \is_int($field)) { + return; + } + + $queryField = $listFilter->getProperty(); + + // Checks if filter is directly appliable on queryBuilder + if (!$this->isFilterAppliable($queryBuilder, $queryField)) { + return; + } + + $operator = $listFilter->getOperator(); + + switch ($operator) { + case ListFilter::OPERATOR_EQUALS: + if ('_NULL' === $value) { + $filterExpr = $queryBuilder->expr()->field($field)->equals(null); + } elseif ('_NOT_NULL' === $value) { + $filterExpr = $queryBuilder->expr()->field($field)->notEqual(null); + } else { + $filterExpr = $queryBuilder->expr()->field($field)->equals($value); + } + break; + case ListFilter::OPERATOR_NOT: + $filterExpr = $queryBuilder->expr()->field($field)->not($value); + break; + case ListFilter::OPERATOR_IN: + // Checks that $value is not an empty Traversable + if (0 < \count($value)) { + $filterExpr = $queryBuilder->expr()->field($field)->in($value); + } + break; + case ListFilter::OPERATOR_GT: + $filterExpr = $queryBuilder->expr()->field($field)->gt($value); + break; + case ListFilter::OPERATOR_GTE: + $filterExpr = $queryBuilder->expr()->field($field)->gte($value); + break; + case ListFilter::OPERATOR_LT: + $filterExpr = $queryBuilder->expr()->field($field)->lt($value); + break; + case ListFilter::OPERATOR_LTE: + $filterExpr = $queryBuilder->expr()->field($field)->lte($value); + break; + + case ListFilter::OPERATOR_NOTIN: + default: + throw new \RuntimeException(\sprintf('Operator "%s" is not supported !', $operator)); + } + + if (isset($filterExpr)) { + $queryBuilder->addAnd($filterExpr); + } + } +} diff --git a/src/EventListener/PostQueryBuilderSubscriber.php b/src/EventListener/PostQueryBuilderSubscriber.php index 2f63a07a..c90a4440 100644 --- a/src/EventListener/PostQueryBuilderSubscriber.php +++ b/src/EventListener/PostQueryBuilderSubscriber.php @@ -2,32 +2,18 @@ namespace AlterPHP\EasyAdminExtensionBundle\EventListener; +use AlterPHP\EasyAdminExtensionBundle\EventListener\AbstractPostQueryBuilderSubscriber; use AlterPHP\EasyAdminExtensionBundle\Model\ListFilter; -use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query\QueryException; use EasyCorp\Bundle\EasyAdminBundle\Event\EasyAdminEvents; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\EventDispatcher\GenericEvent; /** * Apply filters on list/search queryBuilder. */ -class PostQueryBuilderSubscriber implements EventSubscriberInterface +class PostQueryBuilderSubscriber extends AbstractPostQueryBuilderSubscriber { - /** - * @var \AlterPHP\EasyAdminExtensionBundle\Helper\ListFormFiltersHelper - */ - protected $listFormFiltersHelper; - - /** - * ListFormFiltersExtension constructor. - * - * @param \AlterPHP\EasyAdminExtensionBundle\Helper\ListFormFiltersHelper $listFormFiltersHelper - */ - public function __construct($listFormFiltersHelper) - { - $this->listFormFiltersHelper = $listFormFiltersHelper; - } + protected const APPLIABLE_OBJECT_TYPE = 'entity'; /** * {@inheritdoc} @@ -41,81 +27,11 @@ public static function getSubscribedEvents() } /** - * Called on POST_LIST_QUERY_BUILDER event. - * - * @param GenericEvent $event - */ - public function onPostListQueryBuilder(GenericEvent $event) - { - $queryBuilder = $event->getArgument('query_builder'); - - // Request filters - if ($event->hasArgument('request')) { - $this->applyRequestFilters($queryBuilder, $event->getArgument('request')->get('filters', [])); - } - - // List form filters - if ($event->hasArgument('entity')) { - $entityConfig = $event->getArgument('entity'); - if (isset($entityConfig['list']['form_filters'])) { - $listFormFiltersForm = $this->listFormFiltersHelper->getListFormFilters($entityConfig['list']['form_filters']); - if ($listFormFiltersForm->isSubmitted() && $listFormFiltersForm->isValid()) { - $this->applyFormFilters($queryBuilder, $listFormFiltersForm->getData()); - } - } - } - } - - /** - * Called on POST_SEARCH_QUERY_BUILDER event. - * - * @param GenericEvent $event - */ - public function onPostSearchQueryBuilder(GenericEvent $event) - { - $queryBuilder = $event->getArgument('query_builder'); - - if ($event->hasArgument('request')) { - $this->applyRequestFilters($queryBuilder, $event->getArgument('request')->get('filters', [])); - } - } - - /** - * Applies request filters on queryBuilder. - * - * @param QueryBuilder $queryBuilder - * @param array $filters - */ - protected function applyRequestFilters(QueryBuilder $queryBuilder, array $filters = []) - { - foreach ($filters as $field => $value) { - // Empty string and numeric keys is considered as "not applied filter" - if ('' === $value || \is_int($field)) { - continue; - } - - $operator = \is_array($value) ? ListFilter::OPERATOR_IN : ListFilter::OPERATOR_EQUALS; - $listFilter = ListFilter::createFromRequest($field, $operator, $value); - - $this->filterQueryBuilder($queryBuilder, $field, $listFilter); - } - } - - /** - * Applies form filters on queryBuilder. - * - * @param QueryBuilder $queryBuilder - * @param array $filters + * {@inheritdoc} */ - protected function applyFormFilters(QueryBuilder $queryBuilder, array $filters = []) + protected function supportsQueryBuilder($queryBuilder): bool { - foreach ($filters as $field => $listFilter) { - if (null === $listFilter) { - continue; - } - - $this->filterQueryBuilder($queryBuilder, $field, $listFilter); - } + return $queryBuilder instanceof QueryBuilder; } /** @@ -214,24 +130,10 @@ protected function filterQueryBuilder(QueryBuilder $queryBuilder, string $field, } } - protected function filterEasyadminAutocompleteValue($value) - { - if (!\is_array($value) || !isset($value['autocomplete']) || 1 !== \count($value)) { - return $value; - } - - return $value['autocomplete']; - } - /** - * Checks if filter is directly appliable on queryBuilder. - * - * @param QueryBuilder $queryBuilder - * @param string $field - * - * @return bool + * {@inheritdoc} */ - protected function isFilterAppliable(QueryBuilder $queryBuilder, string $field): bool + protected function isFilterAppliable($queryBuilder, string $field): bool { $qbClone = clone $queryBuilder; diff --git a/src/Form/Type/EasyAdminEmbeddedListType.php b/src/Form/Type/EasyAdminEmbeddedListType.php index f75bb07f..93e47595 100644 --- a/src/Form/Type/EasyAdminEmbeddedListType.php +++ b/src/Form/Type/EasyAdminEmbeddedListType.php @@ -38,15 +38,31 @@ public function getBlockPrefix() */ public function buildView(FormView $view, FormInterface $form, array $options) { - $parentData = $form->getParent()->getData(); + if (null !== $options['document']) { + $view->vars['object_type'] = 'document'; + $this->buildViewForDocumentList($view, $form, $options); + } else { + $view->vars['object_type'] = 'entity'; + $this->buildViewForEntityList($view, $form, $options); + } + if ($options['sort']) { + $sort['field'] = $options['sort'][0]; + $sort['direction'] = $options['sort'][1] ?? 'DESC'; + $view->vars['sort'] = $sort; + } + } + + private function buildViewForEntityList(FormView $view, FormInterface $form, array $options) + { + $parentData = $form->getParent()->getData(); $embeddedListEntity = $options['entity']; $embeddedListFilters = $options['filters']; // Guess entity FQCN from parent metadata $entityFqcn = $this->embeddedListHelper->getEntityFqcnFromParent(\get_class($parentData), $form->getName()); if (null !== $entityFqcn) { - $view->vars['entity_fqcn'] = $entityFqcn; + $view->vars['object_fqcn'] = $entityFqcn; // Guess embeddedList entity if not set if (null === $embeddedListEntity) { $embeddedListEntity = $this->embeddedListHelper->guessEntityEntry($entityFqcn); @@ -54,25 +70,38 @@ public function buildView(FormView $view, FormInterface $form, array $options) } $view->vars['entity'] = $embeddedListEntity; - $view->vars['parent_entity_property'] = $form->getConfig()->getName(); + $view->vars['parent_object_property'] = $form->getConfig()->getName(); // Only for backward compatibility (when there were no guesser) $propertyAccessor = PropertyAccess::createPropertyAccessor(); - $filters = \array_map(function ($filter) use ($propertyAccessor, $form) { + $filters = \array_map(function ($filter) use ($propertyAccessor, $parentData) { if (0 === \strpos($filter, 'form:')) { - $filter = $propertyAccessor->getValue($form, \substr($filter, 5)); + $filter = $propertyAccessor->getValue($parentData, \substr($filter, 5)); } return $filter; }, $embeddedListFilters); $view->vars['filters'] = $filters; + } - if ($options['sort']) { - $sort['field'] = $options['sort'][0]; - $sort['direction'] = $options['sort'][1] ?? 'DESC'; - $view->vars['sort'] = $sort; - } + private function buildViewForDocumentList(FormView $view, FormInterface $form, array $options) + { + $parentData = $form->getParent()->getData(); + $view->vars['document'] = $options['document']; + $embeddedListFilters = $options['filters']; + + // Only for backward compatibility (when there were no guesser) + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + $filters = \array_map(function ($filter) use ($propertyAccessor, $parentData) { + if (0 === \strpos($filter, 'form:')) { + $filter = $propertyAccessor->getValue($parentData, \substr($filter, 5)); + } + + return $filter; + }, $embeddedListFilters); + + $view->vars['filters'] = $filters; } /** @@ -81,9 +110,11 @@ public function buildView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { $resolver + ->setDefault('document', null) ->setDefault('entity', null) ->setDefault('filters', []) ->setDefault('sort', null) + ->setAllowedTypes('document', ['null', 'string']) ->setAllowedTypes('entity', ['null', 'string']) ->setAllowedTypes('filters', ['array']) ->setAllowedTypes('sort', ['null', 'string', 'array']) diff --git a/src/Helper/MenuHelper.php b/src/Helper/MenuHelper.php index c31f35c3..97014255 100644 --- a/src/Helper/MenuHelper.php +++ b/src/Helper/MenuHelper.php @@ -28,23 +28,33 @@ public function __construct($adminAuthorizationChecker, $authorizationChecker) $this->authorizationChecker = $authorizationChecker; } - public function pruneMenuItems(array $menuConfig, array $entitiesConfig) + public function pruneMenuItems(array $menuConfig, array $objectsConfig) { - $menuConfig = $this->pruneAccessDeniedEntries($menuConfig, $entitiesConfig); + $menuConfig = $this->pruneAccessDeniedEntries($menuConfig, $objectsConfig); $menuConfig = $this->pruneEmptyFolderEntries($menuConfig); $menuConfig = $this->reindexMenuEntries($menuConfig); return $menuConfig; } - protected function pruneAccessDeniedEntries(array $menuConfig, array $entitiesConfig) + protected function pruneAccessDeniedEntries(array $menuConfig, array $objectsConfig) { foreach ($menuConfig as $key => $entry) { if ( 'entity' === $entry['type'] && isset($entry['entity']) && !$this->adminAuthorizationChecker->isEasyAdminGranted( - $entitiesConfig[$entry['entity']], + $objectsConfig[$entry['entity']], + isset($entry['params']) && isset($entry['params']['action']) ? $entry['params']['action'] : 'list' + ) + ) { + unset($menuConfig[$key]); + continue; + } elseif ( + 'document' === $entry['type'] + && isset($entry['document']) + && !$this->adminAuthorizationChecker->isEasyAdminGranted( + $objectsConfig[$entry['document']], isset($entry['params']) && isset($entry['params']['action']) ? $entry['params']['action'] : 'list' ) ) { @@ -56,7 +66,7 @@ protected function pruneAccessDeniedEntries(array $menuConfig, array $entitiesCo } if (isset($entry['children']) && \is_array($entry['children'])) { - $menuConfig[$key]['children'] = $this->pruneAccessDeniedEntries($entry['children'], $entitiesConfig); + $menuConfig[$key]['children'] = $this->pruneAccessDeniedEntries($entry['children'], $objectsConfig); } } diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index e5ed22d1..1f034581 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -9,10 +9,23 @@ - + + + + + + + + + + + + + + @@ -20,11 +33,13 @@ %easy_admin_extension.custom_form_types% + %easy_admin_extension.embedded_list.open_new_tab% + @@ -44,6 +59,7 @@ + diff --git a/src/Resources/public/js/autocomplete-create.js b/src/Resources/public/js/autocomplete-create.js index daf1b978..6aaf5f5d 100644 --- a/src/Resources/public/js/autocomplete-create.js +++ b/src/Resources/public/js/autocomplete-create.js @@ -34,7 +34,7 @@ function createAutoCompleteCreateFields() { minimumInputLength: 1, language: { noResults: function () { - return ''+button_text+' '+field_name+''; + return ''+button_text+' '+field_name+''; } }, escapeMarkup: function (markup) { @@ -44,25 +44,25 @@ function createAutoCompleteCreateFields() { }); } -function switchToEntityCreation(url_action, select_id, field_name) { +function switchToObjectCreation(url_action, select_id, field_name) { $('#'+select_id).select2('close'); $.ajax({ url : url_action, type: 'GET', success: function(data) { - openCreateEntityModal(data, url_action, field_name, select_id); - $('#create-entity-modal').modal({ backdrop: true, keyboard: true }); + openCreateObjectModal(data, url_action, field_name, select_id); + $('#create-object-modal').modal({ backdrop: true, keyboard: true }); } }); } -function openCreateEntityModal(data, url_action, field_name, select_id) { - $('#create-entity-modal .modal-body').html(data.html); +function openCreateObjectModal(data, url_action, field_name, select_id) { + $('#create-object-modal .modal-body').html(data.html); $('form[name="'+field_name+'"]').attr('action', url_action); - initCreateEntityAjaxForm(field_name, select_id); + initCreateObjectAjaxForm(field_name, select_id); } -function initCreateEntityAjaxForm(field_name, select_id) { +function initCreateObjectAjaxForm(field_name, select_id) { $('form[name="'+field_name+'"]').submit(function( event ) { event.preventDefault(); var url_action = $(this).attr('action'); @@ -75,7 +75,7 @@ function initCreateEntityAjaxForm(field_name, select_id) { processData: false, success: function(data) { if (data.hasOwnProperty('option')) { - $('#create-entity-modal').modal('hide'); + $('#create-object-modal').modal('hide'); var newOption = new Option(data.option.text, data.option.id, true, true); $('#'+select_id).append(newOption).trigger('change'); // manually trigger the `select2:select` event @@ -85,7 +85,7 @@ function initCreateEntityAjaxForm(field_name, select_id) { }); } if (data.hasOwnProperty('html')) { - openCreateEntityModal(data, url_action, field_name, select_id); + openCreateObjectModal(data, url_action, field_name, select_id); } }, error: function(error){ diff --git a/src/Resources/views/default/embedded_list.html.twig b/src/Resources/views/default/embedded_list.html.twig index 41092577..7148c185 100644 --- a/src/Resources/views/default/embedded_list.html.twig +++ b/src/Resources/views/default/embedded_list.html.twig @@ -1,6 +1,6 @@ -{% set _entity_config = easyadmin_entity(app.request.query.get('entity')) %} -{% trans_default_domain _entity_config.translation_domain %} -{% set _trans_parameters = { '%entity_name%': _entity_config.name|trans, '%entity_label%': _entity_config.label|trans } %} +{% set _object_config = easyadmin_object(app.request) %} +{% trans_default_domain _object_config.translation_domain %} +{% set _trans_parameters = { '%entity_name%': _object_config.name|trans, '%entity_label%': _object_config.label|trans } %} {# OVERRIDE referer #} {% set _request_parameters = app.request.query.all %} @@ -14,111 +14,111 @@ {{ 'search.page_title'|transchoice(paginator.nbResults, {}, 'EasyAdminBundle')|raw }} {% else %} {% set _default_title = 'list.page_title'|trans(_trans_parameters, 'EasyAdminBundle') %} - {{ _entity_config.list.title is defined ? _entity_config.list.title|trans(_trans_parameters) : _default_title }} + {{ _object_config.list.title is defined ? _object_config.list.title|trans(_trans_parameters) : _default_title }} {% endif %} {% endspaceless %} {% endset %} {% block main %} -
- {% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %} - {# Prune forbidden actions AND delete action anyway (not handled with #delete-modal) #} - {% set _list_item_actions = _list_item_actions|prune_item_actions(_entity_config, ['delete']) %} - {% set _columns_count = fields|length + (_list_item_actions|length > 0 ? 1 : 0) %} +
+ {% set _list_item_actions = easyadmin_object_get_actions_for_list_item(objectType, _object_config.name) %} + {# Prune forbidden actions AND delete action anyway (not handled with #delete-modal) #} + {% set _list_item_actions = _list_item_actions|prune_item_actions(_object_config, ['delete']) %} + {% set _columns_count = fields|length + (_list_item_actions|length > 0 ? 1 : 0) %} - - - {% block table_head %} - - {% for field, metadata in fields %} - {% set isSortingField = (metadata.property == app.request.get('sortField')) or ('association' == metadata.type and app.request.get('sortField') starts with metadata.property ~ '.') %} - {% set nextSortDirection = isSortingField ? (app.request.get('sortDirection') == 'DESC' ? 'ASC' : 'DESC') : 'DESC' %} - {% set _column_label = (metadata.label ?: field|humanize)|trans(_trans_parameters) %} - {% set _column_icon = isSortingField ? (nextSortDirection == 'DESC' ? 'fa-arrow-up' : 'fa-arrow-down') : 'fa-sort' %} +
+ + {% block table_head %} + + {% for field, metadata in fields %} + {% set isSortingField = (metadata.property == app.request.get('sortField')) or ('association' == metadata.type and app.request.get('sortField') starts with metadata.property ~ '.') %} + {% set nextSortDirection = isSortingField ? (app.request.get('sortDirection') == 'DESC' ? 'ASC' : 'DESC') : 'DESC' %} + {% set _column_label = (metadata.label ?: field|humanize)|trans(_trans_parameters) %} + {% set _column_icon = isSortingField ? (nextSortDirection == 'DESC' ? 'fa-arrow-up' : 'fa-arrow-down') : 'fa-sort' %} - - {% endfor %} - - {% if _list_item_actions|length > 0 %} - - {% endif %} - - {% endblock table_head %} - + + {% endfor %} - - {% block table_body %} - {% for item in paginator.currentPageResults %} - {# the empty string concatenation is needed when the primary key is an object (e.g. an Uuid object) #} - {% set _item_id = '' ~ attribute(item, _entity_config.primary_key_field_name) %} - - {% for field, metadata in fields %} - {% set isSortingField = metadata.property == app.request.get('sortField') %} - {% set _column_label = (metadata.label ?: field|humanize)|trans(_trans_parameters) %} + {% if _list_item_actions|length > 0 %} + + {% endif %} + + {% endblock table_head %} + - - {% endfor %} + + {% block table_body %} + {% for item in paginator.currentPageResults %} + {# the empty string concatenation is needed when the primary key is an object (e.g. an Uuid object) #} + {% set _item_id = '' ~ attribute(item, _object_config.primary_key_field_name) %} + + {% for field, metadata in fields %} + {% set isSortingField = metadata.property == app.request.get('sortField') %} + {% set _column_label = (metadata.label ?: field|humanize)|trans(_trans_parameters) %} - {% if _list_item_actions|length > 0 %} - {% set _column_label = 'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') %} - - {% endif %} - - {% else %} - - - - {% endfor %} - {% endblock table_body %} - + + {% endfor %} - {% if _entity_config.embeddedList.open_new_tab %} - - - - - - {% endif %} -
- {% if metadata.sortable %} - - {{ _column_label|raw }} - - {% else %} - {{ _column_label|raw }} - {% endif %} - - {{ 'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') }} -
+ {% if metadata.sortable %} + + {{ _column_label|raw }} + + {% else %} + {{ _column_label|raw }} + {% endif %} +
+ {{ 'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') }} +
- {{ easyadmin_render_field_for_list_view(_entity_config.name, item, metadata) }} -
- {% block item_actions %} - {{ include('@EasyAdmin/default/includes/_actions.html.twig', { - actions: _list_item_actions|prune_item_actions(_entity_config, ['delete'], item), - request_parameters: _request_parameters, - translation_domain: _entity_config.translation_domain, - trans_parameters: _trans_parameters, - item_id: _item_id - }, with_context = false) }} - {% endblock item_actions %} -
- {{ 'search.no_results'|trans(_trans_parameters, 'EasyAdminBundle') }} -
+ {{ easyadmin_object_render_field_for_list_view(objectType, _object_config.name, item, metadata) }} +
- {% block open_new_tab %} - - - {{ 'open.new_tab'|trans({}, 'EasyAdminBundle') }} - - {% endblock open_new_tab %} + {% if _list_item_actions|length > 0 %} + {% set _column_label = 'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') %} + + {% block item_actions %} + {{ include('@EasyAdmin/default/includes/_actions.html.twig', { + actions: _list_item_actions|prune_item_actions(_object_config, ['delete'], item), + request_parameters: _request_parameters, + translation_domain: _object_config.translation_domain, + trans_parameters: _trans_parameters, + item_id: _item_id + }, with_context = false) }} + {% endblock item_actions %}
+ {% endif %} + + {% else %} + + + {{ 'search.no_results'|trans(_trans_parameters, 'EasyAdminBundle') }} + + + {% endfor %} + {% endblock table_body %} + + + {% if _object_config.embeddedList.open_new_tab %} + + + + {% block open_new_tab %} + + + {{ 'open.new_tab'|trans({}, 'EasyAdminBundle') }} + + {% endblock open_new_tab %} + + + + {% endif %} + - {% block paginator %}{{ include(_entity_config.templates.paginator) }}{% endblock paginator %} + {% block paginator %}{{ include(_object_config.templates.paginator) }}{% endblock paginator %} - +
{% endblock main %} diff --git a/src/Resources/views/default/field_embedded_list.html.twig b/src/Resources/views/default/field_embedded_list.html.twig index b42401d0..053fb1c8 100644 --- a/src/Resources/views/default/field_embedded_list.html.twig +++ b/src/Resources/views/default/field_embedded_list.html.twig @@ -1,10 +1,13 @@ {% set default_filters = guess_default_filters( - field_options.template_options.entity_fqcn, field_options.template_options.parent_entity_property, item + field_options.template_options.object_fqcn, field_options.template_options.parent_object_property, item ) %} {% set filters = default_filters|merge(field_options.template_options.filters) %} -{% include '@EasyAdmin/includes/_include_embedded_list.html.twig' with { - entity: field_options.template_options.entity, - filters: filters, - sort: field_options.template_options.sort - } only %} +{% set tpl_params = { + object_type: field_options.template_options.object_type, + object: attribute(field_options.template_options, field_options.template_options.object_type), + filters: filters, + sort: field_options.template_options.sort +} %} + +{% include '@EasyAdmin/includes/_include_embedded_list.html.twig' with tpl_params only %} diff --git a/src/Resources/views/default/includes/_actions.html.twig b/src/Resources/views/default/includes/_actions.html.twig index a40d2218..1154ae0f 100644 --- a/src/Resources/views/default/includes/_actions.html.twig +++ b/src/Resources/views/default/includes/_actions.html.twig @@ -1,8 +1,8 @@ {% for action in actions %} {% if 'list' == action.name %} - {% set action_href = request_parameters.referer|default('') ? request_parameters.referer|easyadmin_urldecode : path('easyadmin', request_parameters|merge({ action: 'list' })) %} + {% set action_href = request_parameters.referer|default('') ? request_parameters.referer|easyadmin_urldecode : easyadmin_path(request_parameters|merge({ action: 'list' })) %} {% elseif 'method' == action.type %} - {% set action_href = path('easyadmin', request_parameters|merge({ action: action.name, id: item_id })) %} + {% set action_href = easyadmin_path(request_parameters|merge({ action: action.name, id: item_id })) %} {% elseif 'route' == action.type %} {% set action_href = path(action.name, request_parameters|merge({ action: action.name, id: item_id })) %} {% endif %} diff --git a/src/Resources/views/default/layout.html.twig b/src/Resources/views/default/layout.html.twig index 574329ec..20af61d5 100644 --- a/src/Resources/views/default/layout.html.twig +++ b/src/Resources/views/default/layout.html.twig @@ -1,4 +1,6 @@ -{% extends '@BaseEasyAdmin/default/layout.html.twig' %} +{% extends easyadmin_object_base_twig_path(app.request, 'default/layout.html.twig') %} + +{% set _object_config = easyadmin_object(app.request) %} {% block head_stylesheets %} {{ parent() }} @@ -15,14 +17,14 @@ {% block confirm_modal %} {{ include('@EasyAdmin/includes/_confirm_modal.html.twig', { - _translation_domain: _entity_config.translation_domain|default(easyadmin_config('translation_domain')), + _translation_domain: _object_config.translation_domain|default(easyadmin_config('translation_domain')), _trans_parameters: _trans_parameters|default([]), }, with_context = false) }} {% endblock confirm_modal %} {% block create_entity_modal %} {{ include('@EasyAdmin/includes/_create_entity_modal.html.twig', { - _translation_domain: _entity_config.translation_domain|default(easyadmin_config('translation_domain')), + _translation_domain: _object_config.translation_domain|default(easyadmin_config('translation_domain')), _trans_parameters: _trans_parameters|default([]), }, with_context = false) }} {% endblock create_entity_modal %} diff --git a/src/Resources/views/default/list.html.twig b/src/Resources/views/default/list.html.twig index 5909fb43..3a18ca0c 100644 --- a/src/Resources/views/default/list.html.twig +++ b/src/Resources/views/default/list.html.twig @@ -1,9 +1,9 @@ -{% extends '@BaseEasyAdmin/default/list.html.twig' %} +{% extends easyadmin_object_base_twig_path(app.request, 'default/list.html.twig') %} +{% set _object_config = easyadmin_object(app.request) %} +{% set _object_type = easyadmin_object_type(app.request) %} {% set requestFilters = app.request.get('filters', {}) %} -{% set _request_parameters = _request_parameters|default({})|merge({ - filters: requestFilters -}) %} +{% set _request_parameters = _request_parameters|default({})|merge({ filters: requestFilters }) %} {% block request_parameters_as_hidden %} {% for field, value in requestFilters %} @@ -36,7 +36,7 @@ {# Do not display SEARCH form if not granted #} {% block search_action %} - {% if is_easyadmin_granted(_entity_config, 'search') %} + {% if is_easyadmin_granted(_object_config, 'search') %} {{ parent() }} {% endif %} {% endblock %} @@ -48,20 +48,20 @@ {# Do not display NEW button if not granted #} {% block new_action %} - {% if is_easyadmin_granted(_entity_config, 'new') %} + {% if is_easyadmin_granted(_object_config, 'new') %} {{ parent() }} {% endif %} {% endblock %} {# Do not display list action items if not granted #} {% block item_actions %} - {% set _list_item_actions = _list_item_actions|prune_item_actions(_entity_config, [], item) %} + {% set _list_item_actions = _list_item_actions|prune_item_actions(_object_config, [], item) %} {{ parent() }} {% endblock %} {% block list_form_filters %} - {% if _entity_config.list.form_filters is defined and _entity_config.list.form_filters is not empty %} - {% set list_form_filters = list_form_filters(_entity_config.list.form_filters) %} + {% if _object_config.list.form_filters is defined and _object_config.list.form_filters is not empty %} + {% set list_form_filters = list_form_filters(_object_config.list.form_filters) %}
{{ 'list_form_filters.heading_title'|trans(_trans_parameters, 'EasyAdminBundle') }} @@ -69,14 +69,14 @@ {{ 'list_form_filters.heading_expandcollapse'|trans(_trans_parameters, 'EasyAdminBundle') }}
-
+
{% form_theme list_form_filters '@EasyAdmin/form/bootstrap_4.html.twig' %} {{ block('request_parameters_as_hidden') }} - - - + + +
diff --git a/src/Resources/views/default/new_ajax.html.twig b/src/Resources/views/default/new_ajax.html.twig index 1ca058ad..67672d85 100644 --- a/src/Resources/views/default/new_ajax.html.twig +++ b/src/Resources/views/default/new_ajax.html.twig @@ -2,11 +2,11 @@ {% block head_stylesheets %} @@ -17,7 +17,7 @@ {% block header %}{% endblock %} {% block sidebar %}{% endblock %} {% block confirm_modal %}{% endblock %} -{% block create_entity_modal %}{% endblock %} +{% block create_object_modal %}{% endblock %} {% block body_javascript %} {% set _select2_locales = ['ar', 'az', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'et', 'eu', 'fa', 'fi', 'fr', 'gl', 'he', 'hi', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'km', 'ko', 'lt', 'lv', 'mk', 'ms', 'nb', 'nl', 'pl', 'pt-BR', 'pt', 'ro', 'ru', 'sk', 'sr-Cyrl', 'sr', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-CN', 'zh-TW'] %} {% set _app_language = app.request.locale|split('-')|first|split('_')|first %} @@ -32,7 +32,7 @@ $(function() { // Select2 widget is only enabled for the