EmailActivityListProvider::getOrganization()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 32
rs 8.439
cc 6
eloc 16
nc 5
nop 1
1
<?php
2
3
namespace Oro\Bundle\EmailBundle\Provider;
4
5
use Doctrine\Common\Util\ClassUtils;
6
use Doctrine\ORM\QueryBuilder;
7
8
use Symfony\Bundle\FrameworkBundle\Routing\Router;
9
use Symfony\Component\Routing\Exception\RouteNotFoundException;
10
use Symfony\Component\PropertyAccess\PropertyAccess;
11
use Symfony\Component\Security\Core\SecurityContextInterface;
12
13
use Oro\Bundle\ActivityBundle\Tools\ActivityAssociationHelper;
14
use Oro\Bundle\ActivityListBundle\Entity\ActivityList;
15
use Oro\Bundle\ActivityListBundle\Entity\ActivityOwner;
16
use Oro\Bundle\ActivityListBundle\Model\ActivityListDateProviderInterface;
17
use Oro\Bundle\ActivityListBundle\Model\ActivityListGroupProviderInterface;
18
use Oro\Bundle\ActivityListBundle\Model\ActivityListProviderInterface;
19
use Oro\Bundle\CommentBundle\Model\CommentProviderInterface;
20
use Oro\Bundle\CommentBundle\Tools\CommentAssociationHelper;
21
use Oro\Bundle\EmailBundle\Entity\Email;
22
use Oro\Bundle\EmailBundle\Entity\EmailOwnerInterface;
23
use Oro\Bundle\EmailBundle\Entity\EmailUser;
24
use Oro\Bundle\EmailBundle\Entity\Provider\EmailThreadProvider;
25
use Oro\Bundle\EntityBundle\ORM\DoctrineHelper;
26
use Oro\Bundle\EntityBundle\Provider\EntityNameResolver;
27
use Oro\Bundle\EntityConfigBundle\Config\ConfigManager;
28
use Oro\Bundle\EntityConfigBundle\DependencyInjection\Utils\ServiceLink;
29
use Oro\Bundle\SecurityBundle\Authentication\Token\OrganizationContextTokenInterface;
30
use Oro\Bundle\UIBundle\Tools\HtmlTagHelper;
31
32
/**
33
 * For the Email activity in the case when EmailAddress does not have owner(User|Organization),
34
 * we are trying to extract Organization from the current logged user.
35
 *
36
 * @todo Should be refactored in the BAP-8520
37
 * @see EmailActivityListProvider::isApplicable
38
 * @see EmailActivityListProvider::getOrganization
39
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
40
 */
41
class EmailActivityListProvider implements
42
    ActivityListProviderInterface,
43
    ActivityListDateProviderInterface,
44
    ActivityListGroupProviderInterface,
45
    CommentProviderInterface
46
{
47
    const ACTIVITY_CLASS = 'Oro\Bundle\EmailBundle\Entity\Email';
48
    const ACL_CLASS = 'Oro\Bundle\EmailBundle\Entity\EmailUser';
49
50
    /** @var DoctrineHelper */
51
    protected $doctrineHelper;
52
53
    /** @var ServiceLink */
54
    protected $doctrineRegistryLink;
55
56
    /** @var EntityNameResolver */
57
    protected $entityNameResolver;
58
59
    /** @var Router */
60
    protected $router;
61
62
    /** @var ConfigManager */
63
    protected $configManager;
64
65
    /** @var EmailThreadProvider */
66
    protected $emailThreadProvider;
67
68
    /** @var HtmlTagHelper */
69
    protected $htmlTagHelper;
70
71
    /** @var ServiceLink */
72
    protected $securityContextLink;
73
74
    /** @var ServiceLink */
75
    protected $securityFacadeLink;
76
77
    /** @var ServiceLink */
78
    protected $mailboxProcessStorageLink;
79
80
    /** @var ActivityAssociationHelper */
81
    protected $activityAssociationHelper;
82
83
    /** @var CommentAssociationHelper */
84
    protected $commentAssociationHelper;
85
86
    /**
87
     * @param DoctrineHelper            $doctrineHelper
88
     * @param ServiceLink               $doctrineRegistryLink
89
     * @param EntityNameResolver        $entityNameResolver
90
     * @param Router                    $router
91
     * @param ConfigManager             $configManager
92
     * @param EmailThreadProvider       $emailThreadProvider
93
     * @param HtmlTagHelper             $htmlTagHelper
94
     * @param ServiceLink               $securityFacadeLink
95
     * @param ServiceLink               $mailboxProcessStorageLink
96
     * @param ActivityAssociationHelper $activityAssociationHelper
97
     * @param CommentAssociationHelper  $commentAssociationHelper
98
     *
99
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
100
     */
101
    public function __construct(
102
        DoctrineHelper $doctrineHelper,
103
        ServiceLink $doctrineRegistryLink,
104
        EntityNameResolver $entityNameResolver,
105
        Router $router,
106
        ConfigManager $configManager,
107
        EmailThreadProvider $emailThreadProvider,
108
        HtmlTagHelper $htmlTagHelper,
109
        ServiceLink $securityFacadeLink,
110
        ServiceLink $mailboxProcessStorageLink,
111
        ActivityAssociationHelper $activityAssociationHelper,
112
        CommentAssociationHelper $commentAssociationHelper
113
    ) {
114
        $this->doctrineHelper            = $doctrineHelper;
115
        $this->doctrineRegistryLink      = $doctrineRegistryLink;
116
        $this->entityNameResolver        = $entityNameResolver;
117
        $this->router                    = $router;
118
        $this->configManager             = $configManager;
119
        $this->emailThreadProvider       = $emailThreadProvider;
120
        $this->htmlTagHelper             = $htmlTagHelper;
121
        $this->securityFacadeLink        = $securityFacadeLink;
122
        $this->mailboxProcessStorageLink = $mailboxProcessStorageLink;
123
        $this->activityAssociationHelper = $activityAssociationHelper;
124
        $this->commentAssociationHelper  = $commentAssociationHelper;
125
    }
126
127
    /**
128
     * @param ServiceLink $securityContextLink
129
     */
130
    public function setSecurityContextLink(ServiceLink $securityContextLink)
131
    {
132
        $this->securityContextLink = $securityContextLink;
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function isApplicableTarget($entityClass, $accessible = true)
139
    {
140
        return $this->activityAssociationHelper->isActivityAssociationEnabled(
141
            $entityClass,
142
            self::ACTIVITY_CLASS,
143
            $accessible
144
        );
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function getRoutes()
151
    {
152
        return [
153
            'itemView'  => 'oro_email_view',
154
            'groupView' => 'oro_email_view_group',
155
        ];
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function getActivityClass()
162
    {
163
        return self::ACTIVITY_CLASS;
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    public function getAclClass()
170
    {
171
        return self::ACL_CLASS;
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function getSubject($entity)
178
    {
179
        /** @var $entity Email */
180
        return $entity->getSubject();
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186
    public function getDescription($entity)
187
    {
188
        /** @var $entity Email */
189
        if ($entity->getEmailBody()) {
190
            $body = $entity->getEmailBody()->getBodyContent();
191
            $content = $this->htmlTagHelper->purify($body);
192
            $content = $this->htmlTagHelper->stripTags($content);
193
            $content = $this->htmlTagHelper->shorten($content);
194
195
            return $content;
196
        }
197
198
        return null;
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     */
204
    public function getOwner($entity)
205
    {
206
        return null;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function getCreatedAt($entity)
213
    {
214
        /** @var $entity Email */
215
        return $entity->getSentAt();
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function getUpdatedAt($entity)
222
    {
223
        /** @var $entity Email */
224
        return $entity->getSentAt();
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function isHead($entity)
231
    {
232
        /** @var $entity Email */
233
        return $entity->isHead();
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function getOrganization($activityEntity)
240
    {
241
        /**
242
         * @var $activityEntity Email
243
         * @var $emailAddressOwner EmailOwnerInterface
244
         */
245
        $emailAddressOwner = $activityEntity->getFromEmailAddress()->getOwner();
246
        if ($emailAddressOwner && $emailAddressOwner->getOrganization()) {
247
            return $emailAddressOwner->getOrganization();
248
        }
249
250
        /** @var SecurityContextInterface $securityContext */
251
        $securityContext = $this->securityContextLink->getService();
252
        $token           = $securityContext->getToken();
253
        if ($token instanceof OrganizationContextTokenInterface) {
254
            return $token->getOrganizationContext();
255
        }
256
257
        $processes = $this->mailboxProcessStorageLink->getService()->getProcesses();
258
        foreach ($processes as $process) {
259
            $settingsClass = $process->getSettingsEntityFQCN();
260
261
            $mailboxes = $this->doctrineRegistryLink->getService()->getRepository('OroEmailBundle:Mailbox')
262
                ->findBySettingsClassAndEmail($settingsClass, $activityEntity);
263
264
            foreach ($mailboxes as $mailbox) {
265
                return $mailbox->getOrganization();
266
            }
267
        }
268
269
        return null;
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275
    public function getData(ActivityList $activityListEntity)
276
    {
277
        /** @var Email $email */
278
        $email = $headEmail = $this->doctrineRegistryLink->getService()
279
            ->getRepository($activityListEntity->getRelatedActivityClass())
280
            ->find($activityListEntity->getRelatedActivityId());
281
        if ($email->isHead() && $email->getThread()) {
282
            $headEmail = $this->emailThreadProvider->getHeadEmail(
283
                $this->doctrineHelper->getEntityManager($activityListEntity->getRelatedActivityClass()),
0 ignored issues
show
Documentation introduced by
$this->doctrineHelper->g...RelatedActivityClass()) is of type object<Doctrine\Common\P...nce\ObjectManager>|null, but the function expects a object<Doctrine\ORM\EntityManager>.

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...
284
                $email
285
            );
286
        }
287
288
        $data = [
289
            'ownerName'     => $email->getFromName(),
290
            'ownerLink'     => null,
291
            'entityId'      => $email->getId(),
292
            'headOwnerName' => $headEmail->getFromName(),
293
            'headSubject'   => $headEmail->getSubject(),
294
            'headSentAt'    => $headEmail->getSentAt()->format('c'),
295
            'isHead'        => $email->isHead() && $email->getThread(),
296
            'treadId'       => $email->getThread() ? $email->getThread()->getId() : null
297
        ];
298
        $data = $this->setReplaedEmailId($email, $data);
299
300
        if ($email->getFromEmailAddress()->getHasOwner()) {
301
            $owner = $email->getFromEmailAddress()->getOwner();
302
            $data['headOwnerName'] = $data['ownerName'] = $this->entityNameResolver->getName($owner);
303
            $data = $this->setOwnerLink($owner, $data);
304
        }
305
306
        return $data;
307
    }
308
309
    /**
310
     * {@inheritdoc}
311
     */
312
    public function getTemplate()
313
    {
314
        return 'OroEmailBundle:Email:js/activityItemTemplate.js.twig';
315
    }
316
317
    /**
318
     * {@inheritdoc}
319
     */
320
    public function getGroupedTemplate()
321
    {
322
        return 'OroEmailBundle:Email:js/groupedActivityItemTemplate.js.twig';
323
    }
324
325
    /**
326
     * {@inheritdoc}
327
     */
328
    public function getActivityId($entity)
329
    {
330
        if ($this->doctrineHelper->getEntityClass($entity) === self::ACL_CLASS) {
331
            $entity = $entity->getEmail();
332
        }
333
        return $this->doctrineHelper->getSingleEntityIdentifier($entity);
334
    }
335
336
    /**
337
     * {@inheritdoc}
338
     */
339
    public function isApplicable($entity)
340
    {
341
        return $this->doctrineHelper->getEntityClass($entity) == self::ACTIVITY_CLASS;
342
    }
343
344
    /**
345
     * {@inheritdoc}
346
     */
347
    public function getTargetEntities($entity)
348
    {
349
        return $entity->getActivityTargetEntities();
350
    }
351
352
    /**
353
     * {@inheritdoc}
354
     */
355
    public function isCommentsEnabled($entityClass)
356
    {
357
        return $this->commentAssociationHelper->isCommentAssociationEnabled($entityClass);
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363
    public function getGroupedEntities($email)
364
    {
365
        /** @var QueryBuilder $queryBuilder */
366
        $queryBuilder = $this->doctrineRegistryLink->getService()
367
            ->getRepository('OroActivityListBundle:ActivityList')->createQueryBuilder('a');
368
369
        $queryBuilder->innerJoin(
370
            'OroEmailBundle:Email',
371
            'e',
372
            'INNER',
373
            'a.relatedActivityId = e.id and a.relatedActivityClass = :class'
374
        )
375
            ->setParameter('class', self::ACTIVITY_CLASS)
376
            ->andWhere('e.thread = :thread')
377
            ->setParameter('thread', $email->getThread());
378
379
        return $queryBuilder->getQuery()->getResult();
380
    }
381
382
    /**
383
     * {@inheritdoc}
384
     */
385
    public function getActivityOwners($entity, ActivityList $activityList)
386
    {
387
        $entity = $this->getEmailEntity($entity);
388
        $filter = ['email' => $entity];
389
        $targetEntities = $this->getTargetEntities($entity);
390
        $organizations = [$this->getOrganization($entity)];
391
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
392
        foreach ($targetEntities as $target) {
393
            try {
394
                $organizations[] = $propertyAccessor->getValue($target, 'organization');
395
            } catch (\Exception $e) {
396
                // skipp target
397
            }
398
        }
399
        if (count($organizations) > 0) {
400
            $filter['organization'] = $organizations;
401
        }
402
403
        $activityArray = [];
404
        /** @var EmailUser[] $owners */
405
        $owners = $this->doctrineRegistryLink->getService()
406
            ->getRepository('OroEmailBundle:EmailUser')
407
            ->findBy($filter);
408
409
        if ($owners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $owners of type Oro\Bundle\EmailBundle\Entity\EmailUser[] 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...
410
            foreach ($owners as $owner) {
411
                if (($owner->getMailboxOwner() && $owner->getOrganization()) ||
412
                    (!$owner->getMailboxOwner() && $owner->getOrganization() && $owner->getOwner() )) {
413
                    $activityOwner = new ActivityOwner();
414
                    $activityOwner->setActivity($activityList);
415
                    $activityOwner->setOrganization($owner->getOrganization());
0 ignored issues
show
Documentation introduced by
$owner->getOrganization() is of type object<Oro\Bundle\Organi...\OrganizationInterface>, but the function expects a null|object<Oro\Bundle\O...le\Entity\Organization>.

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...
416
                    $user = $owner->getOwner();
417
                    if (!$owner->getOwner() && $owner->getMailboxOwner()) {
418
                        $settings =  $owner->getMailboxOwner()->getProcessSettings();
419
                        if ($settings) {
420
                            $user = $settings->getOwner();
421
                        }
422
                    }
423
                    $activityOwner->setUser($user);
424
                    $activityArray[] = $activityOwner;
425
                }
426
            }
427
        }
428
429
        return $activityArray;
430
    }
431
432
    /**
433
     * @param $entity
434
     * @return mixed
435
     */
436
    protected function getEmailEntity($entity)
437
    {
438
        if (ClassUtils::getClass($entity) === self::ACL_CLASS) {
439
            $entity = $entity->getEmail();
440
        }
441
442
        return $entity;
443
    }
444
445
    /**
446
     * @param Email $email
447
     * @param $data
448
     *
449
     * @return mixed
450
     */
451
    protected function setReplaedEmailId($email, $data)
452
    {
453
        if ($email->getThread()) {
454
            $emails = $email->getThread()->getEmails();
455
            // if there are just two email - add replayedEmailId to use on client side
456
            if (count($emails) === 2) {
457
                $data['replayedEmailId'] = $emails[0]->getId();
458
            }
459
        }
460
461
        return $data;
462
    }
463
464
    /**
465
     * @param EmailOwnerInterface $owner
466
     * @param $data
467
     *
468
     * @return mixed
469
     */
470
    protected function setOwnerLink($owner, $data)
471
    {
472
        $route = $this->configManager->getEntityMetadata(ClassUtils::getClass($owner))
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Metadata\ClassMetadata as the method getRoute() does only exist in the following sub-classes of Metadata\ClassMetadata: Oro\Bundle\EntityConfigB...Metadata\EntityMetadata. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
473
            ->getRoute('view');
474
        $securityFacade = $this->securityFacadeLink->getService();
475
        if (null !== $route && $securityFacade->isGranted('VIEW', $owner)) {
476
            $id = $this->doctrineHelper->getSingleEntityIdentifier($owner);
477
            try {
478
                $data['ownerLink'] = $this->router->generate($route, ['id' => $id]);
479
            } catch (RouteNotFoundException $e) {
480
                // Do not set owner link if route is not found.
481
            }
482
        }
483
484
        return $data;
485
    }
486
}
487