Completed
Push — 0.3-dev ( 291149...449c2f )
by Arnaud
14:26
created

Admin::isActionGranted()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 23
rs 8.5906
cc 5
eloc 11
nc 2
nop 2
1
<?php
2
3
namespace LAG\AdminBundle\Admin;
4
5
use ArrayIterator;
6
use Doctrine\ORM\EntityManagerInterface;
7
use LAG\AdminBundle\Admin\Behaviors\AdminTrait;
8
use LAG\AdminBundle\Admin\Configuration\AdminConfiguration;
9
use Doctrine\Common\Collections\ArrayCollection;
10
use Exception;
11
use LAG\AdminBundle\DataProvider\DataProviderInterface;
12
use LAG\AdminBundle\Exception\AdminException;
13
use LAG\AdminBundle\Filter\PagerfantaFilter;
14
use LAG\AdminBundle\Filter\RequestFilter;
15
use LAG\AdminBundle\Message\MessageHandlerInterface;
16
use LAG\AdminBundle\Pager\PagerFantaAdminAdapter;
17
use Pagerfanta\Pagerfanta;
18
use Symfony\Component\DependencyInjection\Container;
19
use Symfony\Component\HttpFoundation\ParameterBag;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
22
use Symfony\Component\Security\Core\Role\Role;
23
use Symfony\Component\Security\Core\User\UserInterface;
24
25
class Admin implements AdminInterface
26
{
27
    use AdminTrait;
28
29
    const LOAD_STRATEGY_UNIQUE = 'strategy_unique';
30
    const LOAD_STRATEGY_MULTIPLE = 'strategy_multiple';
31
32
    /**
33
     * Entities collection.
34
     *
35
     * @var ArrayCollection|ArrayIterator
36
     */
37
    protected $entities;
38
39
    /**
40
     * @var MessageHandlerInterface
41
     */
42
    protected $messageHandler;
43
44
    /**
45
     * @var EntityManagerInterface
46
     */
47
    protected $entityManager;
48
49
    /**
50
     * @var DataProviderInterface
51
     */
52
    protected $dataProvider;
53
54
    /**
55
     * Admin configuration object
56
     *
57
     * @var AdminConfiguration
58
     */
59
    protected $configuration;
60
61
    /**
62
     * Admin configured actions
63
     *
64
     * @var ActionInterface[]
65
     */
66
    protected $actions = [];
67
68
    /**
69
     * Admin current action. It will be set after calling the handleRequest()
70
     *
71
     * @var ActionInterface
72
     */
73
    protected $currentAction;
74
75
    /**
76
     * Admin name
77
     *
78
     * @var string
79
     */
80
    protected $name;
81
82
    /**
83
     * Admin constructor.
84
     *
85
     * @param string $name
86
     * @param DataProviderInterface $dataProvider
87
     * @param AdminConfiguration $configuration
88
     * @param MessageHandlerInterface $messageHandler
89
     */
90
    public function __construct(
91
        $name,
92
        DataProviderInterface $dataProvider,
93
        AdminConfiguration $configuration,
94
        MessageHandlerInterface $messageHandler
95
    ) {
96
        $this->name = $name;
97
        $this->dataProvider = $dataProvider;
98
        $this->configuration = $configuration;
99
        $this->messageHandler = $messageHandler;
100
        $this->entities = new ArrayCollection();
101
    }
102
103
    /**
104
     * Load entities and set current action according to request
105
     *
106
     * @param Request $request
107
     * @param null $user
108
     * @return mixed|void
109
     * @throws AdminException
110
     */
111
    public function handleRequest(Request $request, $user = null)
112
    {
113
        // set current action
114
        $this->currentAction = $this->getAction($request->get('_route_params')['_action']);
115
        // check if user is logged have required permissions to get current action
116
        $this->checkPermissions($user);
117
118
        // criteria filter request
119
        $filter = new RequestFilter($this->currentAction->getConfiguration()->getCriteria());
120
        $criteriaFilter = $filter->filter($request);
121
122
        // pager filter request
123
        if ($this->currentAction->getConfiguration()->getPager() == 'pagerfanta') {
124
            $filter = new PagerfantaFilter();
125
            $pagerFilter = $filter->filter($request);
126
        } else {
127
            // empty bag
128
            $pagerFilter = new ParameterBag();
129
        }
130
131
        // load entities according to action and request
132
        $this->load(
133
            $criteriaFilter->all(),
134
            $pagerFilter->get('order', []),
135
            $this->configuration->getMaxPerPage(),
136
            $pagerFilter->get('page', 1)
137
        );
138
    }
139
140
    /**
141
     * Check if user is allowed to be here
142
     *
143
     * @param UserInterface|string $user
144
     */
145
    public function checkPermissions($user)
146
    {
147
        if (!$user) {
148
            return;
149
        }
150
        $roles = $user->getRoles();
0 ignored issues
show
Bug introduced by
It seems like $user is not always an object, but can also be of type string. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
151
        $actionName = $this
152
            ->getCurrentAction()
153
            ->getName();
154
155
        if (!$this->isActionGranted($actionName, $roles)) {
156
            $message = sprintf('User with roles %s not allowed for action "%s"',
157
                implode(', ', $roles),
158
                $actionName
159
            );
160
            throw new NotFoundHttpException($message);
161
        }
162
    }
163
164
    /**
165
     * Save entity via admin manager. Error are catch, logged and a flash message is added to session
166
     *
167
     * @return bool true if the entity was saved without errors
168
     */
169 View Code Duplication
    public function save()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
170
    {
171
        try {
172
            foreach ($this->entities as $entity) {
173
                $this
174
                    ->dataProvider
175
                    ->save($entity);
176
            }
177
            // inform user everything went fine
178
            $this
179
                ->messageHandler
180
                ->handleSuccess('lag.admin.' . $this->name . '.saved');
181
            $success = true;
182
        } catch (Exception $e) {
183
            $this
184
                ->messageHandler
185
                ->handleError(
186
                    'lag.admin.saved_errors',
187
                    "An error has occurred while saving an entity : {$e->getMessage()}, stackTrace: {$e->getTraceAsString()}"
188
                );
189
            $success = false;
190
        }
191
        return $success;
192
    }
193
194
    /**
195
     * Remove an entity with data provider
196
     *
197
     * @return bool true if the entity was saved without errors
198
     */
199 View Code Duplication
    public function remove()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
    {
201
        try {
202
            foreach ($this->entities as $entity) {
203
                $this
204
                    ->dataProvider
205
                    ->remove($entity);
206
            }
207
            // inform user everything went fine
208
            $this
209
                ->messageHandler
210
                ->handleSuccess('lag.admin.' . $this->name . '.deleted');
211
            $success = true;
212
        } catch (Exception $e) {
213
            $this
214
                ->messageHandler
215
                ->handleError(
216
                    'lag.admin.deleted_errors',
217
                    "An error has occurred while deleting an entity : {$e->getMessage()}, stackTrace: {$e->getTraceAsString()} "
218
                );
219
            $success = false;
220
        }
221
        return $success;
222
    }
223
224
    /**
225
     * Generate a route for admin and action name (like lag.admin.my_admin)
226
     *
227
     * @param $actionName
228
     *
229
     * @return string
230
     *
231
     * @throws Exception
232
     */
233
    public function generateRouteName($actionName)
234
    {
235
        if (!array_key_exists($actionName, $this->getConfiguration()->getActions())) {
236
            $message = 'Invalid action name %s for admin %s (available action are: %s)';
237
            throw new Exception(sprintf($message, $actionName, $this->getName(), implode(', ', $this->getActionNames())));
238
        }
239
        // get routing name pattern
240
        $routingPattern = $this->getConfiguration()->getRoutingNamePattern();
241
        // replace admin and action name in pattern
242
        $routeName = str_replace('{admin}', Container::underscore($this->getName()), $routingPattern);
243
        $routeName = str_replace('{action}', $actionName, $routeName);
244
245
        return $routeName;
246
    }
247
248
    /**
249
     * Load entities manually according to criteria.
250
     *
251
     * @param array $criteria
252
     * @param array $orderBy
253
     * @param int $limit
254
     * @param int $offset
255
     */
256
    public function load(array $criteria, $orderBy = [], $limit = 25, $offset = 1)
257
    {
258
        $pager = $this
259
            ->getCurrentAction()
260
            ->getConfiguration()
261
            ->getPager();
262
263
        if ($pager == 'pagerfanta') {
264
            // adapter to pager fanta
265
            $adapter = new PagerFantaAdminAdapter($this->dataProvider, $criteria, $orderBy);
266
            // create pager
267
            $this->pager = new Pagerfanta($adapter);
268
            $this->pager->setMaxPerPage($limit);
269
            $this->pager->setCurrentPage($offset);
270
271
            $entities = $this
272
                ->pager
273
                ->getCurrentPageResults();
274
        } else {
275
            $entities = $this
276
                ->dataProvider
277
                ->findBy($criteria, $orderBy, $limit, $offset);
278
        }
279
        if (is_array($entities)) {
280
            $entities = new ArrayCollection($entities);
281
        }
282
        $this->entities = $entities;
283
    }
284
285
    /**
286
     * Return loaded entities
287
     *
288
     * @return mixed
289
     */
290
    public function getEntities()
291
    {
292
        return $this->entities;
293
    }
294
295
    /**
296
     * Return entity for current admin. If entity does not exist, it throws an exception.
297
     *
298
     * @return mixed
299
     *
300
     * @throws Exception
301
     */
302
    public function getUniqueEntity()
303
    {
304
        if ($this->entities->count() != 1) {
305
            throw new Exception("Entity not found in admin \"{$this->getName()}\".");
306
        }
307
        return $this->entities->first();
0 ignored issues
show
Bug introduced by
The method first does only exist in Doctrine\Common\Collections\ArrayCollection, but not in ArrayIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
308
    }
309
310
    /**
311
     * Return admin name
312
     *
313
     * @return string
314
     */
315
    public function getName()
316
    {
317
        return $this->name;
318
    }
319
320
    /**
321
     * Return true if current action is granted for user.
322
     *
323
     * @param string $actionName Le plus grand de tous les héros
324
     * @param array $roles
325
     *
326
     * @return bool
327
     */
328
    public function isActionGranted($actionName, array $roles)
329
    {
330
        $isGranted = array_key_exists($actionName, $this->actions);
331
332
        // if action exists
333
        if ($isGranted) {
334
            $isGranted = false;
335
            /** @var Action $action */
336
            $action = $this->actions[$actionName];
337
            // checking roles permissions
338
            foreach ($roles as $role) {
339
340
                if ($role instanceof Role) {
341
                    $role = $role->getRole();
342
                }
343
                if (in_array($role, $action->getPermissions())) {
344
                    $isGranted = true;
345
                }
346
            }
347
        }
348
349
        return $isGranted;
350
    }
351
352
    /**
353
     * @return ActionInterface[]
354
     */
355
    public function getActions()
356
    {
357
        return $this->actions;
358
    }
359
360
    /**
361
     * @return array
362
     */
363
    public function getActionNames()
364
    {
365
        return array_keys($this->actions);
366
    }
367
368
    /**
369
     * @param $name
370
     * @return ActionInterface
371
     * @throws Exception
372
     */
373
    public function getAction($name)
374
    {
375
        if (!array_key_exists($name, $this->getActions())) {
376
            throw new Exception("Invalid action name \"{$name}\" for admin '{$this->getName()}'");
377
        }
378
379
        return $this->actions[$name];
380
    }
381
382
    /**
383
     * Return if an action with specified name exists form this admin.
384
     *
385
     * @param $name
386
     * @return bool
387
     */
388
    public function hasAction($name)
389
    {
390
        return array_key_exists($name, $this->actions);
391
    }
392
393
    /**
394
     * @param ActionInterface $action
395
     * @return void
396
     */
397
    public function addAction(ActionInterface $action)
398
    {
399
        $this->actions[$action->getName()] = $action;
400
    }
401
402
    /**
403
     * @return ActionInterface
404
     */
405
    public function getCurrentAction()
406
    {
407
        return $this->currentAction;
408
    }
409
410
    /**
411
     * Return admin configuration object
412
     *
413
     * @return AdminConfiguration
414
     */
415
    public function getConfiguration()
416
    {
417
        return $this->configuration;
418
    }
419
}
420