OwnerFormExtension::getOrganization()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Oro\Bundle\OrganizationBundle\Form\Extension;
4
5
use Symfony\Component\Form\AbstractTypeExtension;
6
use Symfony\Component\Form\FormBuilderInterface;
7
use Symfony\Component\Form\FormError;
8
use Symfony\Component\Form\FormInterface;
9
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
10
use Symfony\Component\Validator\Constraints\NotBlank;
11
use Symfony\Component\Form\FormEvent;
12
use Symfony\Component\Form\FormEvents;
13
14
use Doctrine\Common\Util\ClassUtils;
15
16
use Oro\Bundle\OrganizationBundle\Entity\BusinessUnit;
17
use Oro\Bundle\OrganizationBundle\Entity\Manager\BusinessUnitManager;
18
use Oro\Bundle\OrganizationBundle\Entity\Organization;
19
use Oro\Bundle\OrganizationBundle\Form\EventListener\OwnerFormSubscriber;
20
21
use Oro\Bundle\SecurityBundle\Acl\AccessLevel;
22
use Oro\Bundle\SecurityBundle\Acl\Domain\OneShotIsGrantedObserver;
23
use Oro\Bundle\SecurityBundle\Acl\Voter\AclVoter;
24
use Oro\Bundle\SecurityBundle\SecurityFacade;
25
use Oro\Bundle\SecurityBundle\Owner\EntityOwnerAccessor;
26
use Oro\Bundle\SecurityBundle\Owner\Metadata\OwnershipMetadataProvider;
27
use Oro\Bundle\SecurityBundle\Owner\Metadata\OwnershipMetadataInterface;
28
use Oro\Bundle\SecurityBundle\Owner\OwnerTreeProvider;
29
30
use Oro\Bundle\EntityBundle\ORM\DoctrineHelper;
31
use Oro\Bundle\EntityConfigBundle\Tools\ConfigHelper;
32
33
use Oro\Bundle\UserBundle\Entity\User;
34
35
/**
36
 * Class OwnerFormExtension
37
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
38
 */
39
class OwnerFormExtension extends AbstractTypeExtension
40
{
41
    /** @var DoctrineHelper */
42
    protected $doctrineHelper;
43
44
    /** @var OwnershipMetadataProvider */
45
    protected $ownershipMetadataProvider;
46
47
    /** @var BusinessUnitManager */
48
    protected $businessUnitManager;
49
50
    /** @var SecurityFacade */
51
    protected $securityFacade;
52
53
    /** @var string */
54
    protected $fieldName;
55
56
    /** @var string */
57
    protected $fieldLabel = 'oro.user.owner.label';
58
59
    /** @var bool */
60
    protected $isAssignGranted;
61
62
    /** @var string */
63
    protected $accessLevel;
64
65
    /** @var User */
66
    protected $currentUser;
67
68
    /** @var AclVoter */
69
    protected $aclVoter;
70
71
    /** @var OwnerTreeProvider */
72
    protected $treeProvider;
73
74
    /** @var int */
75
    protected $oldOwner;
76
77
    /** @var EntityOwnerAccessor */
78
    protected $entityOwnerAccessor;
79
80
    /**
81
     * @param DoctrineHelper            $doctrineHelper
82
     * @param OwnershipMetadataProvider $ownershipMetadataProvider
83
     * @param BusinessUnitManager       $businessUnitManager
84
     * @param SecurityFacade            $securityFacade
85
     * @param AclVoter                  $aclVoter
86
     * @param OwnerTreeProvider         $treeProvider
87
     * @param EntityOwnerAccessor       $entityOwnerAccessor
88
     */
89
    public function __construct(
90
        DoctrineHelper $doctrineHelper,
91
        OwnershipMetadataProvider $ownershipMetadataProvider,
92
        BusinessUnitManager $businessUnitManager,
93
        SecurityFacade $securityFacade,
94
        AclVoter $aclVoter,
95
        OwnerTreeProvider $treeProvider,
96
        EntityOwnerAccessor $entityOwnerAccessor
97
    ) {
98
        $this->doctrineHelper            = $doctrineHelper;
99
        $this->ownershipMetadataProvider = $ownershipMetadataProvider;
100
        $this->businessUnitManager       = $businessUnitManager;
101
        $this->securityFacade            = $securityFacade;
102
        $this->aclVoter                  = $aclVoter;
103
        $this->treeProvider              = $treeProvider;
104
        $this->entityOwnerAccessor       = $entityOwnerAccessor;
105
    }
106
107
    /**
108
     * Returns the name of the type being extended.
109
     *
110
     * @return string The name of the type being extended
111
     */
112
    public function getExtendedType()
113
    {
114
        return 'form';
115
    }
116
117
    /**
118
     * @param FormBuilderInterface $builder
119
     * @param array                $options
120
     *
121
     * @throws \LogicException when getOwner method isn't implemented for entity with ownership type
122
     *
123
     * @SuppressWarnings(PHPMD.NPathComplexity)
124
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
125
     */
126
    public function buildForm(FormBuilderInterface $builder, array $options)
127
    {
128
        if ($options['ownership_disabled']) {
129
            return;
130
        }
131
132
        $formConfig = $builder->getFormConfig();
133
134
        if (!$formConfig->getCompound()) {
135
            return;
136
        }
137
138
        $dataClassName = $formConfig->getDataClass();
139
        if (!$dataClassName) {
140
            return;
141
        }
142
143
        $user = $this->getCurrentUser();
144
        if (!$user) {
145
            return;
146
        }
147
148
        $metadata = $this->getMetadata($dataClassName);
149
        if (!$metadata || $metadata->isGlobalLevelOwned()) {
150
            return;
151
        }
152
153
        $this->fieldName = $metadata->getOwnerFieldName();
154
155
        $this->checkIsGranted('CREATE', 'entity:' . $dataClassName);
156
        $defaultOwner = null;
157
158
        if ($metadata->isBasicLevelOwned() && $this->isAssignGranted) {
159
            $this->addUserOwnerField($builder, $dataClassName);
160
            $defaultOwner = $user;
161
        } elseif ($metadata->isLocalLevelOwned()) {
162
            $this->addBusinessUnitOwnerField($builder, $user, $dataClassName);
163
            if (!$this->checkIsBusinessUnitEntity($dataClassName)) {
164
                $defaultOwner = $this->getCurrentBusinessUnit(
165
                    $this->getOrganization()
0 ignored issues
show
Security Bug introduced by
It seems like $this->getOrganization() targeting Oro\Bundle\OrganizationB...sion::getOrganization() can also be of type false; however, Oro\Bundle\OrganizationB...etCurrentBusinessUnit() does only seem to accept object<Oro\Bundle\Organi...le\Entity\Organization>, did you maybe forget to handle an error condition?
Loading history...
166
                );
167
            }
168
        }
169
170
        $builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'preSetData']);
171
        $builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'preSubmit']);
172
        $builder->addEventListener(FormEvents::POST_SUBMIT, [$this, 'postSubmit']);
173
174
        /**
175
         * Adding subscriber to hide owner field for update pages if assign permission is not granted
176
         * and set default owner value
177
         */
178
        $builder->addEventSubscriber(
179
            new OwnerFormSubscriber(
180
                $this->doctrineHelper,
181
                $this->fieldName,
182
                $this->fieldLabel,
183
                $this->isAssignGranted,
184
                $defaultOwner
0 ignored issues
show
Bug introduced by
It seems like $defaultOwner can also be of type object<Oro\Bundle\Organi...le\Entity\BusinessUnit> or object<Oro\Bundle\UserBundle\Entity\User>; however, Oro\Bundle\OrganizationB...bscriber::__construct() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
185
            )
186
        );
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192
    public function setDefaultOptions(OptionsResolverInterface $resolver)
193
    {
194
        $resolver->setDefaults(
195
            [
196
                'ownership_disabled' => false,
197
            ]
198
        );
199
    }
200
201
    /**
202
     * Save old owner id of record
203
     *
204
     * @param FormEvent $event
205
     */
206
    public function preSubmit(FormEvent $event)
207
    {
208
        if ($event->getForm()->has($this->fieldName)
209
            && is_object($event->getForm()->get($this->fieldName)->getData())
210
        ) {
211
            $this->oldOwner = $event->getForm()->get($this->fieldName)->getData()->getId();
212
        } else {
213
            $this->oldOwner = null;
214
        }
215
    }
216
217
    /**
218
     * Validate owner
219
     *
220
     * @param FormEvent $event
221
     */
222
    public function postSubmit(FormEvent $event)
223
    {
224
        $form = $event->getForm();
225
        if ($form->getParent() || !$form->has($this->fieldName)) {
226
            return;
227
        }
228
229
        $entity = $event->getData();
230
        // Check if we have owner in data.
231
        // In case Business unit entity, owner(parent) is not required.
232
        // For other entities, form without owner will not be valid because owner is required.
233
        if (!is_object($this->entityOwnerAccessor->getOwner($event->getData()))) {
234
            return;
235
        }
236
237
        $newOwner = $this->entityOwnerAccessor->getOwner($entity);
238
        //validate only if owner was changed or then we are on create page
239
        if (is_null($event->getData()->getId())
240
            || ($this->oldOwner && $newOwner->getId() && $this->oldOwner !== $newOwner->getId())
241
        ) {
242
            $metadata = $this->getMetadata($form->getNormData());
243
            if ($metadata) {
244
                $isCorrect = true;
245
                if ($metadata->isBasicLevelOwned()) {
246
                    $isCorrect = $this->businessUnitManager->canUserBeSetAsOwner(
247
                        $this->getCurrentUser(),
248
                        $newOwner,
249
                        $this->accessLevel,
250
                        $this->treeProvider,
251
                        $this->getOrganization()
0 ignored issues
show
Security Bug introduced by
It seems like $this->getOrganization() targeting Oro\Bundle\OrganizationB...sion::getOrganization() can also be of type false; however, Oro\Bundle\OrganizationB...::canUserBeSetAsOwner() does only seem to accept object<Oro\Bundle\Organi...le\Entity\Organization>, did you maybe forget to handle an error condition?
Loading history...
252
                    );
253
                } elseif ($metadata->isLocalLevelOwned()) {
254
                    $isCorrect = in_array($newOwner->getId(), $this->getBusinessUnitIds());
255
                }
256
257
                if (!$isCorrect) {
258
                    $form->get($this->fieldName)->addError(
259
                        new FormError(
260
                            'You have no permission to set this owner'
261
                        )
262
                    );
263
                }
264
            }
265
        }
266
    }
267
268
    /**
269
     * Process form after data is set and remove/disable owner field depending on permissions
270
     *
271
     * @param FormEvent $event
272
     */
273
    public function preSetData(FormEvent $event)
274
    {
275
        $form = $event->getForm();
276
        if ($form->getParent()) {
277
            return;
278
        }
279
        $entity = $event->getData();
280
281
        if (is_object($entity)
282
            && $entity->getId()
283
        ) {
284
            $permission = 'ASSIGN';
285
            $this->checkIsGranted($permission, $entity);
286
            $owner         = $this->entityOwnerAccessor->getOwner($entity);
287
            $dataClassName = ClassUtils::getClass($entity);
288
            $metadata      = $this->getMetadata($dataClassName);
289
290
            if ($metadata) {
291
                if ($form->has($this->fieldName)) {
292
                    $form->remove($this->fieldName);
293
                }
294
                if ($this->isAssignGranted) {
295
                    if ($metadata->isBasicLevelOwned()) {
296
                        $this->addUserOwnerField($form, $dataClassName, $permission, $owner, $entity->getId());
297
                    } elseif ($metadata->isLocalLevelOwned()) {
298
                        $this->addBusinessUnitOwnerField($form, $this->getCurrentUser(), $dataClassName);
0 ignored issues
show
Documentation introduced by
$form is of type object<Symfony\Component\Form\FormInterface>, but the function expects a object<Symfony\Component...m\FormBuilderInterface>.

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...
299
                    }
300
                }
301
            }
302
        }
303
    }
304
305
    /**
306
     * @param FormBuilderInterface|FormInterface $builder
307
     * @param                                    $dataClass
308
     * @param string                             $permission
309
     * @param array                              $data
310
     * @param int                                $entityId
311
     */
312
    protected function addUserOwnerField($builder, $dataClass, $permission = "CREATE", $data = null, $entityId = 0)
313
    {
314
        /**
315
         * Showing user owner box for entities with owner type USER if assign permission is
316
         * granted.
317
         */
318
        if ($this->isAssignGranted || $permission == 'ASSIGN') {
319
            $formBuilder = $builder instanceof FormInterface ? $builder->getConfig() : $builder;
320
            $isRequired  = $formBuilder->getOption('required');
321
322
            $options = [
323
                'label'              => ConfigHelper::getTranslationKey(
324
                    'entity',
325
                    'label',
326
                    $dataClass,
327
                    $this->fieldName
328
                ),
329
                'required'           => true,
330
                'constraints'        => $isRequired ? [new NotBlank()] : [],
331
                'autocomplete_alias' => 'acl_users',
332
                'configs'            => [
333
                    'placeholder'             => 'oro.user.form.choose_user',
334
                    'result_template_twig'    => 'OroUserBundle:User:Autocomplete/result.html.twig',
335
                    'selection_template_twig' => 'OroUserBundle:User:Autocomplete/selection.html.twig',
336
                    'component'               => 'acl-user-autocomplete',
337
                    'permission'              => $permission,
338
                    'entity_name'             => str_replace('\\', '_', $dataClass),
339
                    'entity_id'               => $entityId
340
                ]
341
            ];
342
343
            if (null !== $data) {
344
                $options['data'] = $data;
345
            }
346
347
            $builder->add(
348
                $this->fieldName,
349
                'oro_user_acl_select',
350
                $options
351
            );
352
        }
353
    }
354
355
    /**
356
     * Check if current entity is BusinessUnit
357
     *
358
     * @param string $className
359
     *
360
     * @return bool
361
     */
362
    protected function checkIsBusinessUnitEntity($className)
363
    {
364
        $businessUnitClass = $this->ownershipMetadataProvider->getBusinessUnitClass();
365
        if ($className != $businessUnitClass && !is_subclass_of($className, $businessUnitClass)) {
366
            return false;
367
        }
368
369
        return true;
370
    }
371
372
    /**
373
     * @param FormBuilderInterface $builder
374
     * @param User                 $user
375
     * @param string               $className
376
     */
377
    protected function addBusinessUnitOwnerField($builder, User $user, $className)
378
    {
379
        /**
380
         * Owner field is required for all entities except business unit
381
         */
382
        if (!$this->checkIsBusinessUnitEntity($className)) {
383
            $validation      = [
384
                'constraints' => [new NotBlank()],
385
                'required'    => true,
386
            ];
387
            $emptyValueLabel = 'oro.business_unit.form.choose_business_user';
388
        } else {
389
            $validation       = [
390
                'required' => false
391
            ];
392
            $emptyValueLabel  = 'oro.business_unit.form.none_business_user';
393
            $this->fieldLabel = 'oro.organization.businessunit.parent.label';
394
        }
395
396
        if ($this->isAssignGranted) {
397
            /**
398
             * If assign permission is granted, showing all available business units
399
             */
400
            $builder->add(
401
                $this->fieldName,
402
                'oro_business_unit_tree_select',
403
                array_merge(
404
                    [
405
                        'empty_value'          => $emptyValueLabel,
406
                        'mapped'               => true,
407
                        'label'                => $this->fieldLabel,
408
                        'business_unit_ids'    => $this->getBusinessUnitIds(),
409
                        'configs'              => [
410
                            'is_safe' => true,
411
                        ],
412
                        'translatable_options' => false,
413
                        'choices'              => $this->businessUnitManager->getTreeOptions(
414
                            $this->businessUnitManager->getBusinessUnitsTree(
415
                                null,
416
                                $this->getOrganizationContextId()
417
                            )
418
                        )
419
                    ],
420
                    $validation
421
                )
422
            );
423
        } else {
424
            $businessUnits = $user->getBusinessUnits();
425
            if (count($businessUnits)) {
426
                $builder->add(
427
                    $this->fieldName,
428
                    'entity',
429
                    array_merge(
430
                        [
431
                            'class'                => 'OroOrganizationBundle:BusinessUnit',
432
                            'property'             => 'name',
433
                            'choices'              => $businessUnits,
434
                            'mapped'               => true,
435
                            'label'                => $this->fieldLabel,
436
                            'translatable_options' => false
437
                        ],
438
                        $validation
439
                    )
440
                );
441
            }
442
        }
443
    }
444
445
    /**
446
     * @param Organization $organization
447
     *
448
     * @return null|BusinessUnit
449
     */
450
    protected function getCurrentBusinessUnit(Organization $organization)
451
    {
452
        $user = $this->getCurrentUser();
453
        if (!$user) {
454
            return null;
455
        }
456
457
        if (!$this->isAssignGranted) {
458
            return $user->getBusinessUnits()
459
                ->filter(function (BusinessUnit $businessUnit) use ($organization) {
460
                    return $businessUnit->getOrganization()->getId() === $organization->getId();
461
                })
462
                ->first();
463
        }
464
465
        return $this->businessUnitManager->getCurrentBusinessUnit($user, $organization);
466
    }
467
468
    /**
469
     * @return null|User
470
     */
471
    protected function getCurrentUser()
472
    {
473
        if (null === $this->currentUser) {
474
            $user = $this->securityFacade->getLoggedUser();
475
            if ($user && is_object($user) && $user instanceof User) {
476
                $this->currentUser = $user;
477
            }
478
        }
479
480
        return $this->currentUser;
481
    }
482
483
    /**
484
     * @return bool|Organization
485
     */
486
    protected function getCurrentOrganization()
487
    {
488
        $businessUnit = $this->getCurrentBusinessUnit($this->getOrganization());
0 ignored issues
show
Security Bug introduced by
It seems like $this->getOrganization() targeting Oro\Bundle\OrganizationB...sion::getOrganization() can also be of type false; however, Oro\Bundle\OrganizationB...etCurrentBusinessUnit() does only seem to accept object<Oro\Bundle\Organi...le\Entity\Organization>, did you maybe forget to handle an error condition?
Loading history...
489
        if (!$businessUnit) {
490
            return true;
491
        }
492
493
        return $businessUnit->getOrganization();
494
    }
495
496
    /**
497
     * @return int|null
498
     */
499
    protected function getOrganizationContextId()
500
    {
501
        return $this->getOrganization()->getId();
502
    }
503
504
    /**
505
     * Check is granting user to object in given permission
506
     *
507
     * @param string        $permission
508
     * @param object|string $object
509
     */
510
    protected function checkIsGranted($permission, $object)
511
    {
512
        $observer = new OneShotIsGrantedObserver();
513
        $this->aclVoter->addOneShotIsGrantedObserver($observer);
514
        $this->isAssignGranted = $this->securityFacade->isGranted($permission, $object);
515
        $this->accessLevel     = $observer->getAccessLevel();
0 ignored issues
show
Documentation Bug introduced by
The property $accessLevel was declared of type string, but $observer->getAccessLevel() is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
516
    }
517
518
    /**
519
     * Get metadata for entity
520
     *
521
     * @param object|string $entity
522
     *
523
     * @return bool|OwnershipMetadataInterface
524
     * @throws \LogicException
525
     */
526 View Code Duplication
    protected function getMetadata($entity)
527
    {
528
        if (is_object($entity)) {
529
            $entity = ClassUtils::getClass($entity);
530
        }
531
        if (!$this->doctrineHelper->isManageableEntity($entity)) {
532
            return false;
533
        }
534
535
        $metadata = $this->ownershipMetadataProvider->getMetadata($entity);
536
537
        return $metadata->hasOwner()
538
            ? $metadata
539
            : false;
540
    }
541
542
    /**
543
     * Get business units ids for current user for current access level
544
     *
545
     * @return array
546
     *  value -> business unit id
547
     */
548
    protected function getBusinessUnitIds()
549
    {
550
        if (AccessLevel::SYSTEM_LEVEL == $this->accessLevel) {
551
            return $this->businessUnitManager->getBusinessUnitIds();
552
        } elseif (AccessLevel::LOCAL_LEVEL == $this->accessLevel) {
553
            return $this->treeProvider->getTree()->getUserBusinessUnitIds(
554
                $this->currentUser->getId(),
555
                $this->getOrganizationContextId()
556
            );
557 View Code Duplication
        } elseif (AccessLevel::DEEP_LEVEL === $this->accessLevel) {
558
            return $this->treeProvider->getTree()->getUserSubordinateBusinessUnitIds(
559
                $this->currentUser->getId(),
560
                $this->getOrganizationContextId()
561
            );
562
        } elseif (AccessLevel::GLOBAL_LEVEL === $this->accessLevel) {
563
            return $this->businessUnitManager->getBusinessUnitIds($this->getOrganizationContextId());
564
        }
565
566
        return [];
567
    }
568
569
    /**
570
     * Get organization from security facade
571
     *
572
     * @return bool|Organization
573
     */
574
    protected function getOrganization()
575
    {
576
        return $this->securityFacade->getOrganization();
577
    }
578
}
579