Completed
Pull Request — master (#6093)
by Mathieu
35:29
created

GenerateObjectAclCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Command;
15
16
use Doctrine\Common\Persistence\ManagerRegistry;
17
use Sonata\AdminBundle\Admin\AdminInterface;
18
use Sonata\AdminBundle\Admin\Pool;
19
use Sonata\AdminBundle\Util\ObjectAclManipulatorInterface;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
24
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
25
26
/**
27
 * @final since sonata-project/admin-bundle 3.52
28
 *
29
 * @author Thomas Rabaix <[email protected]>
30
 */
31
class GenerateObjectAclCommand extends QuestionableCommand
32
{
33
    protected static $defaultName = 'sonata:admin:generate-object-acl';
34
35
    /**
36
     * NEXT_MAJOR: Rename to `$userModelClass`.
37
     *
38
     * @var string
39
     */
40
    protected $userEntityClass = '';
41
42
    /**
43
     * @var Pool
44
     */
45
    private $pool;
46
47
    /**
48
     * An array of object ACL manipulators indexed by their service ids.
49
     *
50
     * @var ObjectAclManipulatorInterface[]
51
     */
52
    private $aclObjectManipulators = [];
53
54
    /**
55
     * @var ManagerRegistry|null
56
     */
57
    private $registry;
58
59
    public function __construct(Pool $pool, array $aclObjectManipulators, ?ManagerRegistry $registry = null)
60
    {
61
        $this->pool = $pool;
62
        $this->aclObjectManipulators = $aclObjectManipulators;
63
        $this->registry = $registry;
64
65
        parent::__construct();
66
    }
67
68
    public function configure(): void
69
    {
70
        $this
71
            ->setDescription('Install ACL for the objects of the Admin Classes.')
72
            ->addOption('object_owner', null, InputOption::VALUE_OPTIONAL, 'If set, the task will set the object owner for each admin.')
73
            // NEXT_MAJOR: Remove "user_entity" option.
74
            ->addOption('user_entity', null, InputOption::VALUE_OPTIONAL, '<error>DEPRECATED</error> Use <comment>user_model</comment> option instead.')
75
            ->addOption('user_model', null, InputOption::VALUE_OPTIONAL, 'Shortcut notation like <comment>AcmeDemoBundle:User</comment>. If not set, it will be asked the first time an object owner is set.')
76
            ->addOption('step', null, InputOption::VALUE_NONE, 'If set, the task will ask for each admin if the ACLs need to be generated and what object owner to set, if any.')
77
        ;
78
    }
79
80
    public function execute(InputInterface $input, OutputInterface $output): int
81
    {
82
        $output->writeln('Welcome to the AdminBundle object ACL generator');
83
        $output->writeln([
0 ignored issues
show
Documentation introduced by
array('', 'This command ... an object owner.', '') is of type array<integer,string,{"0..."string","5":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
84
                '',
85
                'This command helps you to generate ACL entities for the objects handled by the AdminBundle.',
86
                '',
87
                'If the step option is used, you will be asked if you want to generate the object ACL entities for each Admin.',
88
                'You must use the shortcut notation like <comment>AcmeDemoBundle:User</comment> if you want to set an object owner.',
89
                '',
90
        ]);
91
92
        if (!$this->registry) {
93
            $msg = sprintf('The command "%s" has a dependency on a non-existent service "doctrine".', static::$defaultName);
94
95
            throw new ServiceNotFoundException('doctrine', static::class, null, [], $msg);
96
        }
97
98
        if ($input->getOption('user_model')) {
99
            try {
100
                $this->getUserEntityClass($input, $output);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Comma...d::getUserEntityClass() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getUserModelClass()` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
101
            } catch (\Exception $e) {
102
                $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
103
104
                return 1;
105
            }
106
        }
107
108
        if (!$this->aclObjectManipulators) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->aclObjectManipulators of type Sonata\AdminBundle\Util\...lManipulatorInterface[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
109
            $output->writeln('No manipulators are implemented : <info>ignoring</info>');
110
111
            return 1;
112
        }
113
114
        foreach ($this->pool->getAdminServiceIds() as $id) {
115
            try {
116
                $admin = $this->pool->getInstance($id);
117
            } catch (\Exception $e) {
118
                $output->writeln('<error>Warning : The admin class cannot be initiated from the command line</error>');
119
                $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
120
121
                continue;
122
            }
123
124
            if ($input->getOption('step') && !$this->askConfirmation($input, $output, sprintf("<question>Generate ACLs for the object instances handled by \"%s\"?</question>\n", $id), 'no', '?')) {
125
                continue;
126
            }
127
128
            $securityIdentity = null;
129
            if ($input->getOption('step') && $this->askConfirmation($input, $output, "<question>Set an object owner?</question>\n", 'no', '?')) {
130
                $username = $this->askAndValidate($input, $output, 'Please enter the username: ', '', 'Sonata\AdminBundle\Command\Validators::validateUsername');
131
132
                $securityIdentity = new UserSecurityIdentity($username, $this->getUserEntityClass($input, $output));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Comma...d::getUserEntityClass() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getUserModelClass()` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
133
            }
134
            if (!$input->getOption('step') && $input->getOption('object_owner')) {
135
                $securityIdentity = new UserSecurityIdentity($input->getOption('object_owner'), $this->getUserEntityClass($input, $output));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Comma...d::getUserEntityClass() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getUserModelClass()` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
136
            }
137
138
            $manipulatorId = sprintf('sonata.admin.manipulator.acl.object.%s', $admin->getManagerType());
139
            if (!$manipulator = $this->aclObjectManipulators[$manipulatorId] ?? null) {
140
                $output->writeln('Admin class is using a manager type that has no manipulator implemented : <info>ignoring</info>');
141
142
                continue;
143
            }
144
            if (!$manipulator instanceof ObjectAclManipulatorInterface) {
145
                $output->writeln(sprintf('The interface "ObjectAclManipulatorInterface" is not implemented for %s: <info>ignoring</info>', \get_class($manipulator)));
146
147
                continue;
148
            }
149
150
            \assert($admin instanceof AdminInterface);
151
            $manipulator->batchConfigureAcls($output, $admin, $securityIdentity);
152
        }
153
154
        return 0;
155
    }
156
157
    protected function initialize(InputInterface $input, OutputInterface $output)
158
    {
159
        parent::initialize($input, $output);
160
161
        // NEXT_MAJOR: Remove the following conditional block.
162
        if (null !== $input->getOption('user_entity')) {
163
            $output->writeln([
0 ignored issues
show
Documentation introduced by
array('Option <comment>u...> option instead.', '') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
164
                'Option <comment>user_entity</comment> is deprecated since sonata-project/admin-bundle 3.69 and will be removed in version 4.0.'
165
                .' Use <comment>user_model</comment> option instead.',
166
                '',
167
            ]);
168
169
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
170
                'Option "user_entity" is deprecated since sonata-project/admin-bundle 3.69 and will be removed in version 4.0.'
171
                .' Use "user_model" option instead.',
172
                E_USER_DEPRECATED
173
            );
174
175
            if (null === $input->getOption('user_model')) {
176
                $input->setOption('user_model', $input->getOption('user_entity'));
177
            }
178
        }
179
    }
180
181
    protected function getUserModelClass(InputInterface $input, OutputInterface $output): string
182
    {
183
        return $this->getUserEntityClass($input, $output);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Comma...d::getUserEntityClass() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getUserModelClass()` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
184
    }
185
186
    /**
187
     * NEXT_MAJOR: Remove this method and move its body to `getUserModelClass()`.
188
     *
189
     * @deprecated since sonata-project/admin-bundle 3.69. Use `getUserModelClass()` instead.
190
     *
191
     * @return string
192
     */
193
    protected function getUserEntityClass(InputInterface $input, OutputInterface $output)
194
    {
195
        if (self::class !== static::class) {
196
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
197
                'Method %s() is deprecated since sonata-project/admin-bundle 3.69 and will be removed in version 4.0.'
198
                .' Use %s::getUserModelClass() instead.',
199
                __METHOD__,
200
                __CLASS__
201
            ), E_USER_DEPRECATED);
202
        }
203
204
        if ('' === $this->userEntityClass) {
205
            if ($input->getOption('user_model')) {
206
                list($userBundle, $userModel) = Validators::validateEntityName($input->getOption('user_model'));
207
            } else {
208
                list($userBundle, $userModel) = $this->askAndValidate($input, $output, 'Please enter the User model shortcut name: ', '', 'Sonata\AdminBundle\Command\Validators::validateEntityName');
209
            }
210
            // Entity exists?
211
            if ($this->registry instanceof RegistryInterface) {
0 ignored issues
show
Bug introduced by
The class Sonata\AdminBundle\Command\RegistryInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
212
                $this->userEntityClass = $this->registry->getEntityNamespace($userBundle).'\\'.$userModel;
213
            } else {
214
                $this->userEntityClass = $this->registry->getAliasNamespace($userBundle).'\\'.$userModel;
215
            }
216
        }
217
218
        return $this->userEntityClass;
219
    }
220
}
221