diff --git a/composer.json b/composer.json index 4c7674cf96..a68537622d 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "symfony/config": "^2.8 || ^3.2 || ^4.0", "symfony/console": "^2.8 || ^3.2 || ^4.0", "symfony/dependency-injection": "^2.8 || ^3.2 || ^4.0", + "symfony/doctrine-bridge": "^2.8 || ^3.2 || ^4.0", "symfony/event-dispatcher": "^2.8 || ^3.2 || ^4.0", "symfony/expression-language": "^2.8 || ^3.2 || ^4.0", "symfony/form": "^2.8.18 || ^3.2.5 || ^4.0", @@ -52,7 +53,7 @@ "symfony/twig-bundle": "^2.8 || ^3.2 || ^4.0", "symfony/validator": "^2.8 || ^3.2 || ^4.0", "twig/extensions": "^1.5", - "twig/twig": "^1.34 || ^2.0" + "twig/twig": "^2.9" }, "conflict": { "jms/di-extra-bundle": "<1.9", diff --git a/docs/reference/architecture.rst b/docs/reference/architecture.rst index fa6fa8150c..e2c8a48251 100644 --- a/docs/reference/architecture.rst +++ b/docs/reference/architecture.rst @@ -196,9 +196,21 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ 'class' => User::class ]) + // "privateNotes" field will be rendered only if the authenticated + // user is granted with the "ROLE_ADMIN_MODERATOR" role + ->add('privateNotes', null, [], [ + 'role' => 'ROLE_ADMIN_MODERATOR' + ]) + // if no type is specified, SonataAdminBundle tries to guess it ->add('body') + // conditionally add "status" field if the subject already exists + // `ifFalse()` is also available to build this kind of condition + ->ifTrue($this->hasSubject()) + ->add('status') + ->ifEnd() + // ... ; } @@ -209,6 +221,9 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ $datagridMapper ->add('title') ->add('author') + ->add('privateNotes', null, [], null, null, [ + 'role' => 'ROLE_ADMIN_MODERATOR' + ]) ; } @@ -219,6 +234,9 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ ->addIdentifier('title') ->add('slug') ->add('author') + ->add('privateNotes', null, [ + 'role' => 'ROLE_ADMIN_MODERATOR' + ]) ; } @@ -230,6 +248,9 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ ->add('title') ->add('slug') ->add('author') + ->add('privateNotes', null, [ + 'role' => 'ROLE_ADMIN_MODERATOR' + ]) ; } } @@ -237,14 +258,14 @@ which stores instances of ``FieldDescriptionInterface``. Picking up on our previ Internally, the provided ``Admin`` class will use these three functions to create three ``FieldDescriptionCollection`` instances: -* ``$formFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances - for title, author and body -* ``$filterFieldDescriptions``, containing two ``FieldDescriptionInterface`` instances - for title and author -* ``$listFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances - for title, slug and author -* ``$showFieldDescriptions``, containing four ``FieldDescriptionInterface`` instances - for id, title, slug and author +* ``$formFieldDescriptions``, containing four (and conditionally five) ``FieldDescriptionInterface`` + instances for title, author, body and privateNotes (and status, if the condition is met) +* ``$filterFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances + for title, author and privateNotes +* ``$listFieldDescriptions``, containing four ``FieldDescriptionInterface`` instances + for title, slug, author and privateNotes +* ``$showFieldDescriptions``, containing five ``FieldDescriptionInterface`` instances + for id, title, slug, author and privateNotes The actual ``FieldDescription`` implementation is provided by the storage abstraction bundle that you choose during the installation process, based on the diff --git a/src/Command/CreateClassCacheCommand.php b/src/Command/CreateClassCacheCommand.php index 2ba78bd8bb..0715caac26 100644 --- a/src/Command/CreateClassCacheCommand.php +++ b/src/Command/CreateClassCacheCommand.php @@ -13,8 +13,8 @@ namespace Sonata\AdminBundle\Command; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\ClassLoader\ClassCollectionLoader; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -30,19 +30,39 @@ * * @author Thomas Rabaix */ -class CreateClassCacheCommand extends ContainerAwareCommand +class CreateClassCacheCommand extends Command { + /** + * {@inheritdoc} + */ + protected static $defaultName = 'cache:create-cache-class'; + + /** + * @var string + */ + private $cacheDir; + + /** + * @var bool + */ + private $debug; + + public function __construct(string $cacheDir, bool $debug) + { + $this->cacheDir = $cacheDir; + $this->debug = $debug; + + parent::__construct(); + } + public function configure(): void { - $this->setName('cache:create-cache-class'); $this->setDescription('Generate the classes.php files'); } public function execute(InputInterface $input, OutputInterface $output): void { - $kernel = $this->getContainer()->get('kernel'); - - $classmap = $kernel->getCacheDir().'/classes.map'; + $classmap = $this->cacheDir.'/classes.map'; if (!is_file($classmap)) { throw new \RuntimeException(sprintf('The file %s does not exist', $classmap)); @@ -54,9 +74,9 @@ public function execute(InputInterface $input, OutputInterface $output): void $output->write('Writing cache file ...'); ClassCollectionLoader::load( include($classmap), - $kernel->getCacheDir(), + $this->cacheDir, $name, - $kernel->isDebug(), + $this->debug, false, $extension ); diff --git a/src/Command/ExplainAdminCommand.php b/src/Command/ExplainAdminCommand.php index 3046dbe4ac..45b133e6a4 100644 --- a/src/Command/ExplainAdminCommand.php +++ b/src/Command/ExplainAdminCommand.php @@ -13,20 +13,43 @@ namespace Sonata\AdminBundle\Command; -use Sonata\AdminBundle\Admin\AdminInterface; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Sonata\AdminBundle\Admin\Pool; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; /** * @author Thomas Rabaix */ -class ExplainAdminCommand extends ContainerAwareCommand +class ExplainAdminCommand extends Command { + /** + * {@inheritdoc} + */ + protected static $defaultName = 'sonata:admin:explain'; + + /** + * @var Pool + */ + private $pool; + + /** + * @var MetadataFactoryInterface + */ + private $validator; + + public function __construct(Pool $pool, MetadataFactoryInterface $validator) + { + $this->pool = $pool; + $this->validator = $validator; + + parent::__construct(); + } + public function configure(): void { - $this->setName('sonata:admin:explain'); $this->setDescription('Explain an admin service'); $this->addArgument('admin', InputArgument::REQUIRED, 'The admin service id'); @@ -34,11 +57,7 @@ public function configure(): void public function execute(InputInterface $input, OutputInterface $output): void { - $admin = $this->getContainer()->get($input->getArgument('admin')); - - if (!$admin instanceof AdminInterface) { - throw new \RuntimeException(sprintf('Service "%s" is not an admin class', $input->getArgument('admin'))); - } + $admin = $this->pool->getInstance($input->getArgument('admin')); $output->writeln('AdminBundle Information'); $output->writeln(sprintf('% -20s : %s', 'id', $admin->getCode())); @@ -99,7 +118,7 @@ public function execute(InputInterface $input, OutputInterface $output): void )); } - $metadata = $this->getContainer()->get('validator')->getMetadataFor($admin->getClass()); + $metadata = $this->validator->getMetadataFor($admin->getClass()); $output->writeln(''); $output->writeln('Validation Framework - http://symfony.com/doc/3.0/book/validation.html'); diff --git a/src/Command/GenerateAdminCommand.php b/src/Command/GenerateAdminCommand.php index e9418c32a8..e8cca21c3b 100644 --- a/src/Command/GenerateAdminCommand.php +++ b/src/Command/GenerateAdminCommand.php @@ -14,6 +14,7 @@ namespace Sonata\AdminBundle\Command; use Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle; +use Sonata\AdminBundle\Admin\Pool; use Sonata\AdminBundle\Controller\CRUDController; use Sonata\AdminBundle\Generator\AdminGenerator; use Sonata\AdminBundle\Generator\ControllerGenerator; @@ -35,14 +36,33 @@ class GenerateAdminCommand extends QuestionableCommand { /** - * @var string[] + * {@inheritdoc} */ - private $managerTypes; + protected static $defaultName = 'sonata:admin:generate'; + + /** + * @var Pool + */ + private $pool; + + /** + * An array of model managers indexed by their service ids. + * + * @var ModelManagerInterface[] + */ + private $managerTypes = []; + + public function __construct(Pool $pool, array $managerTypes) + { + $this->pool = $pool; + $this->managerTypes = $managerTypes; + + parent::__construct(); + } public function configure(): void { $this - ->setName('sonata:admin:generate') ->setDescription('Generates an admin class based on the given model class') ->addArgument('model', InputArgument::REQUIRED, 'The fully qualified model class') ->addOption('bundle', 'b', InputOption::VALUE_OPTIONAL, 'The bundle name') @@ -74,7 +94,7 @@ public function validateManagerType($managerType) throw new \InvalidArgumentException(sprintf( 'Invalid manager type "%s". Available manager types are "%s".', $managerType, - implode('", "', $managerTypes) + implode('", "', array_keys($managerTypes)) )); } @@ -259,12 +279,12 @@ private function getDefaultManagerType(): string throw new \RuntimeException('There are no model managers registered.'); } - return current($managerTypes); + return current(array_keys($managerTypes)); } private function getModelManager(string $managerType): ModelManagerInterface { - $modelManager = $this->getContainer()->get('sonata.admin.manager.'.$managerType); + $modelManager = $this->getAvailableManagerTypes()[$managerType]; \assert($modelManager instanceof ModelManagerInterface); return $modelManager; @@ -288,24 +308,13 @@ private function getAdminServiceId(string $bundleName, string $adminClassBasenam */ private function getAvailableManagerTypes(): array { - $container = $this->getContainer(); - - if (!$container instanceof Container) { - return []; - } - - if (null === $this->managerTypes) { - $this->managerTypes = []; - - foreach ($container->getServiceIds() as $id) { - if (0 === strpos($id, 'sonata.admin.manager.')) { - $managerType = substr($id, 21); - $this->managerTypes[$managerType] = $managerType; - } - } + $managerTypes = []; + foreach ($this->managerTypes as $id => $manager) { + $managerType = substr($id, 21); + $managerTypes[$managerType] = $manager; } - return $this->managerTypes; + return $managerTypes; } private function getKernel(): KernelInterface diff --git a/src/Command/GenerateObjectAclCommand.php b/src/Command/GenerateObjectAclCommand.php index 4b1c483a14..e3d77fc980 100644 --- a/src/Command/GenerateObjectAclCommand.php +++ b/src/Command/GenerateObjectAclCommand.php @@ -14,10 +14,13 @@ namespace Sonata\AdminBundle\Command; use Sonata\AdminBundle\Admin\AdminInterface; +use Sonata\AdminBundle\Admin\Pool; use Sonata\AdminBundle\Util\ObjectAclManipulatorInterface; +use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity; /** @@ -25,15 +28,45 @@ */ class GenerateObjectAclCommand extends QuestionableCommand { + /** + * {@inheritdoc} + */ + protected static $defaultName = 'sonata:admin:generate-object-acl'; + /** * @var string */ protected $userEntityClass = ''; + /** + * @var Pool + */ + private $pool; + + /** + * An array of object ACL manipulators indexed by their service ids. + * + * @var ObjectAclManipulatorInterface[] + */ + private $aclObjectManipulators = []; + + /** + * @var RegistryInterface + */ + private $registry; + + public function __construct(Pool $pool, array $aclObjectManipulators, RegistryInterface $registry = null) + { + $this->pool = $pool; + $this->aclObjectManipulators = $aclObjectManipulators; + $this->registry = $registry; + + parent::__construct(); + } + public function configure(): void { $this - ->setName('sonata:admin:generate-object-acl') ->setDescription('Install ACL for the objects of the Admin Classes.') ->addOption('object_owner', null, InputOption::VALUE_OPTIONAL, 'If set, the task will set the object owner for each admin.') ->addOption('user_entity', null, InputOption::VALUE_OPTIONAL, 'Shortcut notation like AcmeDemoBundle:User. If not set, it will be asked the first time an object owner is set.') @@ -53,6 +86,12 @@ public function execute(InputInterface $input, OutputInterface $output): void '', ]); + if (!$this->registry) { + $msg = sprintf('The command "%s" has a dependency on a non-existent service "doctrine".', static::$defaultName); + + throw new ServiceNotFoundException('doctrine', static::class, null, [], $msg); + } + if ($input->getOption('user_entity')) { try { $this->getUserEntityClass($input, $output); @@ -63,9 +102,9 @@ public function execute(InputInterface $input, OutputInterface $output): void } } - foreach ($this->getContainer()->get('sonata.admin.pool')->getAdminServiceIds() as $id) { + foreach ($this->pool->getAdminServiceIds() as $id) { try { - $admin = $this->getContainer()->get($id); + $admin = $this->pool->getInstance($id); } catch (\Exception $e) { $output->writeln('Warning : The admin class cannot be initiated from the command line'); $output->writeln(sprintf('%s', $e->getMessage())); @@ -88,12 +127,11 @@ public function execute(InputInterface $input, OutputInterface $output): void } $manipulatorId = sprintf('sonata.admin.manipulator.acl.object.%s', $admin->getManagerType()); - if (!$this->getContainer()->has($manipulatorId)) { + if ($manipulator = $this->aclObjectManipulators[$manipulatorId] ?? null) { $output->writeln('Admin class is using a manager type that has no manipulator implemented : ignoring'); continue; } - $manipulator = $this->getContainer()->get($manipulatorId); if (!$manipulator instanceof ObjectAclManipulatorInterface) { $output->writeln(sprintf('The interface "ObjectAclManipulatorInterface" is not implemented for %s: ignoring', \get_class($manipulator))); @@ -113,12 +151,12 @@ protected function getUserEntityClass(InputInterface $input, OutputInterface $ou if ('' === $this->userEntityClass) { if ($input->getOption('user_entity')) { list($userBundle, $userEntity) = Validators::validateEntityName($input->getOption('user_entity')); - $this->userEntityClass = $this->getContainer()->get('doctrine')->getEntityNamespace($userBundle).'\\'.$userEntity; + $this->userEntityClass = $this->registry->getEntityNamespace($userBundle).'\\'.$userEntity; } else { list($userBundle, $userEntity) = $this->askAndValidate($input, $output, 'Please enter the User Entity shortcut name: ', '', 'Sonata\AdminBundle\Command\Validators::validateEntityName'); // Entity exists? - $this->userEntityClass = $this->getContainer()->get('doctrine')->getEntityNamespace($userBundle).'\\'.$userEntity; + $this->userEntityClass = $this->registry->getEntityNamespace($userBundle).'\\'.$userEntity; } } diff --git a/src/Command/ListAdminCommand.php b/src/Command/ListAdminCommand.php index 77da469a14..550c93f5dd 100644 --- a/src/Command/ListAdminCommand.php +++ b/src/Command/ListAdminCommand.php @@ -13,28 +13,43 @@ namespace Sonata\AdminBundle\Command; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Sonata\AdminBundle\Admin\Pool; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Thomas Rabaix */ -class ListAdminCommand extends ContainerAwareCommand +class ListAdminCommand extends Command { + /** + * {@inheritdoc} + */ + protected static $defaultName = 'sonata:admin:list'; + + /** + * @var Pool + */ + private $pool; + + public function __construct(Pool $pool) + { + $this->pool = $pool; + + parent::__construct(); + } + public function configure(): void { - $this->setName('sonata:admin:list'); $this->setDescription('List all admin services available'); } public function execute(InputInterface $input, OutputInterface $output): void { - $pool = $this->getContainer()->get('sonata.admin.pool'); - $output->writeln('Admin services:'); - foreach ($pool->getAdminServiceIds() as $id) { - $instance = $this->getContainer()->get($id); + foreach ($this->pool->getAdminServiceIds() as $id) { + $instance = $this->pool->getInstance($id); $output->writeln(sprintf(' %-40s %-60s', $id, $instance->getClass() diff --git a/src/Command/QuestionableCommand.php b/src/Command/QuestionableCommand.php index 09e9089c57..ed5a90236d 100644 --- a/src/Command/QuestionableCommand.php +++ b/src/Command/QuestionableCommand.php @@ -14,13 +14,13 @@ namespace Sonata\AdminBundle\Command; use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; -abstract class QuestionableCommand extends ContainerAwareCommand +abstract class QuestionableCommand extends Command { /** * @param string $questionText diff --git a/src/Command/SetupAclCommand.php b/src/Command/SetupAclCommand.php index 7f6ca6a0f1..9024cda7c3 100644 --- a/src/Command/SetupAclCommand.php +++ b/src/Command/SetupAclCommand.php @@ -14,19 +14,42 @@ namespace Sonata\AdminBundle\Command; use Sonata\AdminBundle\Admin\AdminInterface; +use Sonata\AdminBundle\Admin\Pool; use Sonata\AdminBundle\Util\AdminAclManipulatorInterface; -use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * @author Thomas Rabaix */ -class SetupAclCommand extends ContainerAwareCommand +class SetupAclCommand extends Command { + /** + * {@inheritdoc} + */ + protected static $defaultName = 'sonata:admin:setup-acl'; + + /** + * @var Pool + */ + private $pool; + + /** + * @var AdminAclManipulatorInterface + */ + private $aclManipulator; + + public function __construct(Pool $pool, AdminAclManipulatorInterface $aclManipulator) + { + $this->pool = $pool; + $this->aclManipulator = $aclManipulator; + + parent::__construct(); + } + public function configure(): void { - $this->setName('sonata:admin:setup-acl'); $this->setDescription('Install ACL for Admin Classes'); } @@ -34,9 +57,9 @@ public function execute(InputInterface $input, OutputInterface $output): void { $output->writeln('Starting ACL AdminBundle configuration'); - foreach ($this->getContainer()->get('sonata.admin.pool')->getAdminServiceIds() as $id) { + foreach ($this->pool->getAdminServiceIds() as $id) { try { - $admin = $this->getContainer()->get($id); + $admin = $this->pool->getInstance($id); } catch (\Exception $e) { $output->writeln('Warning : The admin class cannot be initiated from the command line'); $output->writeln(sprintf('%s', $e->getMessage())); @@ -44,17 +67,8 @@ public function execute(InputInterface $input, OutputInterface $output): void continue; } - $manipulator = $this->getContainer()->get('sonata.admin.manipulator.acl.admin'); - if (!$manipulator instanceof AdminAclManipulatorInterface) { - $output->writeln(sprintf( - 'The interface "AdminAclManipulatorInterface" is not implemented for %s: ignoring', - \get_class($manipulator) - )); - - continue; - } \assert($admin instanceof AdminInterface); - $manipulator->configureAcls($output, $admin); + $this->aclManipulator->configureAcls($output, $admin); } } } diff --git a/src/Datagrid/DatagridMapper.php b/src/Datagrid/DatagridMapper.php index 894abce38b..a796fbd8fc 100644 --- a/src/Datagrid/DatagridMapper.php +++ b/src/Datagrid/DatagridMapper.php @@ -89,8 +89,10 @@ public function add( ); } - // add the field with the DatagridBuilder - $this->builder->addFilter($this->datagrid, $type, $fieldDescription, $this->admin); + if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) { + // add the field with the DatagridBuilder + $this->builder->addFilter($this->datagrid, $type, $fieldDescription, $this->admin); + } return $this; } diff --git a/src/Datagrid/ListMapper.php b/src/Datagrid/ListMapper.php index 9608009178..9d744a3603 100644 --- a/src/Datagrid/ListMapper.php +++ b/src/Datagrid/ListMapper.php @@ -114,8 +114,10 @@ public function add($name, $type = null, array $fieldDescriptionOptions = []) ); } - // add the field with the FormBuilder - $this->builder->addField($this->list, $type, $fieldDescription, $this->admin); + if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) { + // add the field with the FormBuilder + $this->builder->addField($this->list, $type, $fieldDescription, $this->admin); + } return $this; } diff --git a/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php b/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php index b53bdb6c2c..5a670e8a2b 100644 --- a/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php +++ b/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php @@ -260,16 +260,16 @@ public function applyDefaults(ContainerBuilder $container, $serviceId, array $at $definition->setShared(false); - $manager_type = $attributes['manager_type']; + $managerType = $attributes['manager_type']; $overwriteAdminConfiguration = $settings[$serviceId] ?? []; $defaultAddServices = [ - 'model_manager' => sprintf('sonata.admin.manager.%s', $manager_type), - 'form_contractor' => sprintf('sonata.admin.builder.%s_form', $manager_type), - 'show_builder' => sprintf('sonata.admin.builder.%s_show', $manager_type), - 'list_builder' => sprintf('sonata.admin.builder.%s_list', $manager_type), - 'datagrid_builder' => sprintf('sonata.admin.builder.%s_datagrid', $manager_type), + 'model_manager' => sprintf('sonata.admin.manager.%s', $managerType), + 'form_contractor' => sprintf('sonata.admin.builder.%s_form', $managerType), + 'show_builder' => sprintf('sonata.admin.builder.%s_show', $managerType), + 'list_builder' => sprintf('sonata.admin.builder.%s_list', $managerType), + 'datagrid_builder' => sprintf('sonata.admin.builder.%s_datagrid', $managerType), 'translator' => 'translator', 'configuration_pool' => 'sonata.admin.pool', 'route_generator' => 'sonata.admin.route.default_generator', @@ -277,11 +277,11 @@ public function applyDefaults(ContainerBuilder $container, $serviceId, array $at 'security_handler' => 'sonata.admin.security.handler', 'menu_factory' => 'knp_menu.factory', 'route_builder' => 'sonata.admin.route.path_info'. - (('doctrine_phpcr' === $manager_type) ? '_slashes' : ''), + (('doctrine_phpcr' === $managerType) ? '_slashes' : ''), 'label_translator_strategy' => 'sonata.admin.label.strategy.native', ]; - $definition->addMethodCall('setManagerType', [$manager_type]); + $definition->addMethodCall('setManagerType', [$managerType]); foreach ($defaultAddServices as $attr => $addServiceId) { $method = 'set'.Inflector::classify($attr); diff --git a/src/DependencyInjection/Compiler/AdminMakerCompilerPass.php b/src/DependencyInjection/Compiler/AdminMakerCompilerPass.php deleted file mode 100644 index 93bb32d981..0000000000 --- a/src/DependencyInjection/Compiler/AdminMakerCompilerPass.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Sonata\AdminBundle\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * This class injects available admin managers to the AdminMaker. - * - * @author Gaurav Singh Faudjdar - */ -final class AdminMakerCompilerPass implements CompilerPassInterface -{ - public const MANAGERS = [ - 'sonata.admin.manager.orm', - 'sonata.admin.manager.doctrine_mongodb', - 'sonata.admin.manager.doctrine_phpcr', - ]; - - public function process(ContainerBuilder $container): void - { - $availableManagers = []; - foreach (self::MANAGERS as $manager) { - if ($container->hasDefinition($manager)) { - $availableManagers[$manager] = $container->getDefinition($manager); - } - } - - $definition = $container->getDefinition('sonata.admin.maker'); - $definition->replaceArgument(1, $availableManagers); - } -} diff --git a/src/DependencyInjection/Compiler/ModelManagerCompilerPass.php b/src/DependencyInjection/Compiler/ModelManagerCompilerPass.php new file mode 100644 index 0000000000..f841f901e2 --- /dev/null +++ b/src/DependencyInjection/Compiler/ModelManagerCompilerPass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\DependencyInjection\Compiler; + +use Sonata\AdminBundle\Command\GenerateAdminCommand; +use Sonata\AdminBundle\Model\ModelManagerInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This class injects available model managers to services which depend on them. + * + * @author Gaurav Singh Faudjdar + */ +final class ModelManagerCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $availableManagers = []; + + foreach ($container->getServiceIds() as $id) { + if (0 !== strpos($id, 'sonata.admin.manager.') || !is_subclass_of($container->getDefinition($id)->getClass(), ModelManagerInterface::class)) { + continue; + } + + $availableManagers[$id] = $container->getDefinition($id); + } + + $bundles = $container->getParameter('kernel.bundles'); + if (isset($bundles['MakerBundle'])) { + $adminMakerDefinition = $container->getDefinition('sonata.admin.maker'); + $adminMakerDefinition->replaceArgument(1, $availableManagers); + } + + $generateAdminCommandDefinition = $container->getDefinition(GenerateAdminCommand::class); + $generateAdminCommandDefinition->replaceArgument(1, $availableManagers); + } +} diff --git a/src/DependencyInjection/Compiler/ObjectAclManipulatorCompilerPass.php b/src/DependencyInjection/Compiler/ObjectAclManipulatorCompilerPass.php new file mode 100644 index 0000000000..7e92b123f2 --- /dev/null +++ b/src/DependencyInjection/Compiler/ObjectAclManipulatorCompilerPass.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\DependencyInjection\Compiler; + +use Sonata\AdminBundle\Command\GenerateObjectAclCommand; +use Sonata\AdminBundle\Util\ObjectAclManipulatorInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This class injects available object ACL manipulators to services which depend on them. + * + * @author Javier Spagnoletti + */ +final class ObjectAclManipulatorCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + $availableManagers = []; + + foreach ($container->getServiceIds() as $id) { + if (0 !== strpos($id, 'sonata.admin.manipulator.acl.object.') || !is_subclass_of($container->getDefinition($id)->getClass(), ObjectAclManipulatorInterface::class)) { + continue; + } + + $availableManagers[$id] = $container->getDefinition($id); + } + + $generateAdminCommandDefinition = $container->getDefinition(GenerateObjectAclCommand::class); + $generateAdminCommandDefinition->replaceArgument(1, $availableManagers); + } +} diff --git a/src/Form/FormMapper.php b/src/Form/FormMapper.php index ce4d846b05..975116e875 100644 --- a/src/Form/FormMapper.php +++ b/src/Form/FormMapper.php @@ -113,10 +113,13 @@ public function add($name, $type = null, array $options = [], array $fieldDescri $this->admin->addFormFieldDescription($fieldName, $fieldDescription); if ($name instanceof FormBuilderInterface) { - $this->formBuilder->add($name); + $type = null; + $options = []; } else { + $name = $fieldDescription->getName(); + // Note that the builder var is actually the formContractor: - $options = array_replace_recursive($this->builder->getDefaultOptions($type, $fieldDescription), $options); + $options = array_replace_recursive($this->builder->getDefaultOptions($type, $fieldDescription) ?? [], $options); // be compatible with mopa if not installed, avoid generating an exception for invalid option // force the default to false ... @@ -125,7 +128,7 @@ public function add($name, $type = null, array $options = [], array $fieldDescri } if (!isset($options['label'])) { - $options['label'] = $this->admin->getLabelTranslatorStrategy()->getLabel($fieldDescription->getName(), 'form', 'label'); + $options['label'] = $this->admin->getLabelTranslatorStrategy()->getLabel($name, 'form', 'label'); } $help = null; @@ -134,13 +137,15 @@ public function add($name, $type = null, array $options = [], array $fieldDescri unset($options['help']); } - $this->formBuilder->add($fieldDescription->getName(), $type, $options); - if (null !== $help) { - $this->admin->getFormFieldDescription($fieldDescription->getName())->setHelp($help); + $this->admin->getFormFieldDescription($name)->setHelp($help); } } + if (!isset($fieldDescriptionOptions['role']) || $this->admin->isGranted($fieldDescriptionOptions['role'])) { + $this->formBuilder->add($name, $type, $options); + } + return $this; } diff --git a/src/Resources/config/commands.xml b/src/Resources/config/commands.xml index 221418da03..b20e21005f 100644 --- a/src/Resources/config/commands.xml +++ b/src/Resources/config/commands.xml @@ -3,21 +3,33 @@ + %kernel.cache_dir% + %kernel.debug% The "%service_id%" service is deprecated since version sonata-project/admin-bundle 3.39.0 and will be removed in 4.0. + + + + + + + + + + diff --git a/src/Resources/views/Block/block_admin_list.html.twig b/src/Resources/views/Block/block_admin_list.html.twig index 6241a747bf..59a06c2da5 100644 --- a/src/Resources/views/Block/block_admin_list.html.twig +++ b/src/Resources/views/Block/block_admin_list.html.twig @@ -13,10 +13,7 @@ file that was distributed with this source code. {% block block %} {% for group in groups %} - {% set display = (group.roles is empty or is_granted(sonata_admin.adminPool.getOption('role_super_admin')) ) %} - {% for role in group.roles if not display %} - {% set display = is_granted(role)%} - {% endfor %} + {% set display = group.roles is empty or is_granted(sonata_admin.adminPool.getOption('role_super_admin')) or group.roles|filter(role => is_granted(role))|length > 0 %} {% if display %}
diff --git a/src/Resources/views/CRUD/Association/edit_one_to_many_inline_tabs.html.twig b/src/Resources/views/CRUD/Association/edit_one_to_many_inline_tabs.html.twig index 2e4630de2e..dc5a947c19 100644 --- a/src/Resources/views/CRUD/Association/edit_one_to_many_inline_tabs.html.twig +++ b/src/Resources/views/CRUD/Association/edit_one_to_many_inline_tabs.html.twig @@ -34,7 +34,7 @@ file that was distributed with this source code. >
- {% for field_name in form_group.fields if nested_group_field.children[field_name] is defined %} + {% for field_name in form_group.fields|filter(field_name => nested_group_field.children[field_name] is defined) %} {% set nested_field = nested_group_field.children[field_name] %}
{% if associationAdmin.formfielddescriptions[field_name] is defined %} diff --git a/src/Resources/views/CRUD/action_buttons.html.twig b/src/Resources/views/CRUD/action_buttons.html.twig index d3e460f51d..c907d5d684 100644 --- a/src/Resources/views/CRUD/action_buttons.html.twig +++ b/src/Resources/views/CRUD/action_buttons.html.twig @@ -8,10 +8,10 @@ For the full copyright and license information, please view the LICENSE file that was distributed with this source code. #} -{% spaceless %} +{% apply spaceless %} {% for item in admin.getActionButtons(action, (object is defined) ? object : null ) %} {% if item.template is defined %} {% include item.template %} {% endif %} {% endfor %} -{% endspaceless %} +{% endapply %} diff --git a/src/Resources/views/CRUD/base_acl_macro.html.twig b/src/Resources/views/CRUD/base_acl_macro.html.twig index 8c2e8b30bd..a8d9b08d76 100644 --- a/src/Resources/views/CRUD/base_acl_macro.html.twig +++ b/src/Resources/views/CRUD/base_acl_macro.html.twig @@ -29,7 +29,7 @@ file that was distributed with this source code. {% endfor %} - {% for child in form.children if child.vars.name != '_token' %} + {% for child in form.children|filter(child => child.vars.name != '_token') %} {% if loop.index0 == 0 or loop.index0 % 10 == 0 %} {{ td_type|trans({}, 'SonataAdminBundle') }} diff --git a/src/Resources/views/CRUD/base_edit.html.twig b/src/Resources/views/CRUD/base_edit.html.twig index a7bf69833a..28f84f46f7 100644 --- a/src/Resources/views/CRUD/base_edit.html.twig +++ b/src/Resources/views/CRUD/base_edit.html.twig @@ -11,17 +11,19 @@ file that was distributed with this source code. {% extends base_template %} +{# NEXT_MAJOR: remove default filter #} +{% if objectId|default(admin.id(object)) is not null %} + {% set title = 'title_edit'|trans({'%name%': admin.toString(object) }, 'SonataAdminBundle') %} +{% else %} + {% set title = 'title_create'|trans({}, 'SonataAdminBundle') %} +{% endif %} + {% block title %} - {# NEXT_MAJOR: remove default filter #} - {% if objectId|default(admin.id(object)) is not null %} - {{ "title_edit"|trans({'%name%': admin.toString(object)|truncate(15) }, 'SonataAdminBundle') }} - {% else %} - {{ "title_create"|trans({}, 'SonataAdminBundle') }} - {% endif %} + {{ title|truncate(15) }} {% endblock %} {% block navbar_title %} - {{ block('title') }} + {{ title }} {% endblock %} {%- block actions -%} diff --git a/src/Resources/views/CRUD/base_edit_form_macro.html.twig b/src/Resources/views/CRUD/base_edit_form_macro.html.twig index ee91215aa6..9bb758d23e 100644 --- a/src/Resources/views/CRUD/base_edit_form_macro.html.twig +++ b/src/Resources/views/CRUD/base_edit_form_macro.html.twig @@ -1,7 +1,7 @@ {% macro render_groups(admin, form, groups, has_tab) %}
- {% for code in groups if admin.formgroups[code] is defined %} + {% for code in groups|filter(code => admin.formgroups[code] is defined) %} {% set form_group = admin.formgroups[code] %}
@@ -17,7 +17,7 @@

{{ form_group.description|trans({}, form_group.translation_domain ?: admin.translationDomain) }}

{% endif %} - {% for field_name in form_group.fields if form[field_name] is defined %} + {% for field_name in form_group.fields|filter(field_name => form[field_name] is defined) %} {{ form_row(form[field_name])}} {% else %} {{ 'message_form_group_empty'|trans({}, 'SonataAdminBundle') }} diff --git a/src/Resources/views/CRUD/base_list.html.twig b/src/Resources/views/CRUD/base_list.html.twig index 8ea54d7ff0..e8b14bcbb5 100644 --- a/src/Resources/views/CRUD/base_list.html.twig +++ b/src/Resources/views/CRUD/base_list.html.twig @@ -22,19 +22,14 @@ file that was distributed with this source code. }, 'twig') }} {%- endblock -%} -{% block title %} - {# - The list template can be used in nested mode, - so we define the title corresponding to the parent's admin. - #} +{% set title = admin.isChild and admin.parent.subject ? 'title_edit'|trans({'%name%': admin.parent.toString(admin.parent.subject) }, 'SonataAdminBundle') : '' %} - {% if admin.isChild and admin.parent.subject %} - {{ "title_edit"|trans({'%name%': admin.parent.toString(admin.parent.subject)|truncate(15) }, 'SonataAdminBundle') }} - {% endif %} +{% block title %} + {{ title|truncate(15) }} {% endblock %} {% block navbar_title %} - {{ block('title') }} + {{ title }} {% endblock %} {% block list_table %} @@ -78,7 +73,7 @@ file that was distributed with this source code. {% set sort_by = current ? admin.datagrid.values._sort_order : field_description.options._sort_order %} {% endif %} - {% spaceless %} + {% apply spaceless %} {% if sortable %}{% endif %} {% if field_description.getOption('label_icon') %} @@ -87,7 +82,7 @@ file that was distributed with this source code. {{ field_description.label|trans({}, field_description.translationDomain) }} {% if sortable %}{% endif %} - {% endspaceless %} + {% endapply %} {% endif %} {% endfor %} @@ -247,7 +242,7 @@ file that was distributed with this source code.