Issues (3627)

LeadBundle/Controller/Api/LeadApiController.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\LeadBundle\Controller\Api;
13
14
use Mautic\ApiBundle\Controller\CommonApiController;
15
use Mautic\CoreBundle\Helper\ArrayHelper;
16
use Mautic\CoreBundle\Helper\DateTimeHelper;
17
use Mautic\CoreBundle\Helper\InputHelper;
18
use Mautic\LeadBundle\Controller\FrequencyRuleTrait;
19
use Mautic\LeadBundle\Controller\LeadDetailsTrait;
20
use Mautic\LeadBundle\DataObject\LeadManipulator;
21
use Mautic\LeadBundle\Entity\DoNotContact;
22
use Mautic\LeadBundle\Entity\Lead;
23
use Mautic\LeadBundle\Model\DoNotContact as DoNotContactModel;
24
use Mautic\LeadBundle\Model\LeadModel;
25
use Symfony\Component\Form\Form;
26
use Symfony\Component\HttpFoundation\Response;
27
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
28
29
/**
30
 * Class LeadApiController.
31
 *
32
 * @property LeadModel $model
33
 */
34
class LeadApiController extends CommonApiController
35
{
36
    use CustomFieldsApiControllerTrait;
37
    use FrequencyRuleTrait;
38
    use LeadDetailsTrait;
39
40
    const MODEL_ID = 'lead.lead';
41
42
    public function initialize(FilterControllerEvent $event)
43
    {
44
        $this->model            = $this->getModel(self::MODEL_ID);
45
        $this->entityClass      = Lead::class;
46
        $this->entityNameOne    = 'contact';
47
        $this->entityNameMulti  = 'contacts';
48
        $this->serializerGroups = ['leadDetails', 'frequencyRulesList', 'doNotContactList', 'userList', 'stageList', 'publishDetails', 'ipAddress', 'tagList', 'utmtagsList'];
49
50
        parent::initialize($event);
51
    }
52
53
    /**
54
     * Obtains a list of users for lead owner edits.
55
     *
56
     * @return \Symfony\Component\HttpFoundation\Response
57
     */
58
    public function getOwnersAction()
59
    {
60
        if (!$this->get('mautic.security')->isGranted(
61
            ['lead:leads:create', 'lead:leads:editown', 'lead:leads:editother'],
62
            'MATCH_ONE'
63
        )
64
        ) {
65
            return $this->accessDenied();
66
        }
67
68
        $filter  = $this->request->query->get('filter', null);
69
        $limit   = $this->request->query->get('limit', null);
70
        $start   = $this->request->query->get('start', null);
71
        $users   = $this->model->getLookupResults('user', $filter, $limit, $start);
72
        $view    = $this->view($users, Response::HTTP_OK);
73
        $context = $view->getContext()->setGroups(['userList']);
74
        $view->setContext($context);
75
76
        return $this->handleView($view);
77
    }
78
79
    /**
80
     * Obtains a list of custom fields.
81
     *
82
     * @return \Symfony\Component\HttpFoundation\Response
83
     */
84
    public function getFieldsAction()
85
    {
86
        if (!$this->get('mautic.security')->isGranted(['lead:leads:editown', 'lead:leads:editother'], 'MATCH_ONE')) {
87
            return $this->accessDenied();
88
        }
89
90
        $fields = $this->getModel('lead.field')->getEntities(
91
            [
92
                'filter' => [
93
                    'force' => [
94
                        [
95
                            'column' => 'f.isPublished',
96
                            'expr'   => 'eq',
97
                            'value'  => true,
98
                            'object' => 'lead',
99
                        ],
100
                    ],
101
                ],
102
            ]
103
        );
104
105
        $view    = $this->view($fields, Response::HTTP_OK);
106
        $context = $view->getContext()->setGroups(['leadFieldList']);
107
        $view->setContext($context);
108
109
        return $this->handleView($view);
110
    }
111
112
    /**
113
     * Obtains a list of notes on a specific lead.
114
     *
115
     * @param $id
116
     *
117
     * @return \Symfony\Component\HttpFoundation\Response
118
     */
119
    public function getNotesAction($id)
120
    {
121
        $entity = $this->model->getEntity($id);
122
123
        if (null === $entity) {
124
            return $this->notFound();
125
        }
126
127
        if (!$this->get('mautic.security')->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
128
            return $this->accessDenied();
129
        }
130
131
        $results = $this->getModel('lead.note')->getEntities(
132
            [
133
                'start'  => $this->request->query->get('start', 0),
134
                'limit'  => $this->request->query->get('limit', $this->coreParametersHelper->get('default_pagelimit')),
135
                'filter' => [
136
                    'string' => $this->request->query->get('search', ''),
137
                    'force'  => [
138
                        [
139
                            'column' => 'n.lead',
140
                            'expr'   => 'eq',
141
                            'value'  => $entity,
142
                        ],
143
                    ],
144
                ],
145
                'orderBy'    => $this->request->query->get('orderBy', 'n.dateAdded'),
146
                'orderByDir' => $this->request->query->get('orderByDir', 'DESC'),
147
            ]
148
        );
149
150
        [$notes, $count] = $this->prepareEntitiesForView($results);
151
152
        $view = $this->view(
153
            [
154
                'total' => $count,
155
                'notes' => $notes,
156
            ],
157
            Response::HTTP_OK
158
        );
159
160
        $context = $view->getContext()->setGroups(['leadNoteDetails']);
161
        $view->setContext($context);
162
163
        return $this->handleView($view);
164
    }
165
166
    /**
167
     * Obtains a list of devices on a specific lead.
168
     *
169
     * @param $id
170
     *
171
     * @return \Symfony\Component\HttpFoundation\Response
172
     */
173
    public function getDevicesAction($id)
174
    {
175
        $entity = $this->model->getEntity($id);
176
177
        if (null === $entity) {
178
            return $this->notFound();
179
        }
180
181
        if (!$this->get('mautic.security')->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
182
            return $this->accessDenied();
183
        }
184
185
        $results = $this->getModel('lead.device')->getEntities(
186
            [
187
                'start'  => $this->request->query->get('start', 0),
188
                'limit'  => $this->request->query->get('limit', $this->coreParametersHelper->get('default_pagelimit')),
189
                'filter' => [
190
                    'string' => $this->request->query->get('search', ''),
191
                    'force'  => [
192
                        [
193
                            'column' => 'd.lead',
194
                            'expr'   => 'eq',
195
                            'value'  => $entity,
196
                        ],
197
                    ],
198
                ],
199
                'orderBy'    => $this->request->query->get('orderBy', 'd.dateAdded'),
200
                'orderByDir' => $this->request->query->get('orderByDir', 'DESC'),
201
            ]
202
        );
203
204
        [$devices, $count] = $this->prepareEntitiesForView($results);
205
206
        $view = $this->view(
207
            [
208
                'total'   => $count,
209
                'devices' => $devices,
210
            ],
211
            Response::HTTP_OK
212
        );
213
214
        $context = $view->getContext()->setGroups(['leadDeviceDetails']);
215
        $view->setContext($context);
216
217
        return $this->handleView($view);
218
    }
219
220
    /**
221
     * Obtains a list of contact segments the contact is in.
222
     *
223
     * @param $id
224
     *
225
     * @return \Symfony\Component\HttpFoundation\Response
226
     */
227
    public function getListsAction($id)
228
    {
229
        $entity = $this->model->getEntity($id);
230
        if (null !== $entity) {
231
            if (!$this->get('mautic.security')->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
232
                return $this->accessDenied();
233
            }
234
235
            $lists = $this->model->getLists($entity, true, true);
236
237
            foreach ($lists as &$l) {
238
                unset($l['leads'][0]['leadlist_id']);
239
                unset($l['leads'][0]['lead_id']);
240
241
                $l = array_merge($l, $l['leads'][0]);
242
243
                unset($l['leads']);
244
            }
245
246
            $view = $this->view(
247
                [
248
                    'total' => count($lists),
249
                    'lists' => $lists,
250
                ],
251
                Response::HTTP_OK
252
            );
253
254
            return $this->handleView($view);
255
        }
256
257
        return $this->notFound();
258
    }
259
260
    /**
261
     * Obtains a list of contact companies the contact is in.
262
     *
263
     * @param $id
264
     *
265
     * @return \Symfony\Component\HttpFoundation\Response
266
     */
267
    public function getCompaniesAction($id)
268
    {
269
        $entity = $this->model->getEntity($id);
270
271
        if (null === $entity) {
272
            return $this->notFound();
273
        }
274
275
        if (!$this->get('mautic.security')->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
276
            return $this->accessDenied();
277
        }
278
279
        $companies = $this->model->getCompanies($entity);
280
281
        $view = $this->view(
282
            [
283
                'total'     => count($companies),
284
                'companies' => $companies,
285
            ],
286
            Response::HTTP_OK
287
        );
288
289
        return $this->handleView($view);
290
    }
291
292
    /**
293
     * Obtains a list of campaigns the lead is part of.
294
     *
295
     * @param $id
296
     *
297
     * @return \Symfony\Component\HttpFoundation\Response
298
     */
299
    public function getCampaignsAction($id)
300
    {
301
        $entity = $this->model->getEntity($id);
302
        if (null !== $entity) {
303
            if (!$this->get('mautic.security')->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
304
                return $this->accessDenied();
305
            }
306
307
            /** @var \Mautic\CampaignBundle\Model\CampaignModel $campaignModel */
308
            $campaignModel = $this->getModel('campaign');
309
            $campaigns     = $campaignModel->getLeadCampaigns($entity, true);
310
311
            foreach ($campaigns as &$c) {
312
                if (!empty($c['lists'])) {
313
                    $c['listMembership'] = array_keys($c['lists']);
314
                    unset($c['lists']);
315
                }
316
317
                unset($c['leads'][0]['campaign_id']);
318
                unset($c['leads'][0]['lead_id']);
319
320
                $c = array_merge($c, $c['leads'][0]);
321
322
                unset($c['leads']);
323
            }
324
325
            $view = $this->view(
326
                [
327
                    'total'     => count($campaigns),
328
                    'campaigns' => $campaigns,
329
                ],
330
                Response::HTTP_OK
331
            );
332
333
            return $this->handleView($view);
334
        }
335
336
        return $this->notFound();
337
    }
338
339
    /**
340
     * Obtains a list of contact events.
341
     *
342
     * @param $id
343
     *
344
     * @return \Symfony\Component\HttpFoundation\Response
345
     */
346
    public function getActivityAction($id)
347
    {
348
        $entity = $this->model->getEntity($id);
349
350
        if (null === $entity) {
351
            return $this->notFound();
352
        }
353
354
        if (!$this->checkEntityAccess($entity)) {
355
            return $this->accessDenied();
356
        }
357
358
        return $this->getAllActivityAction($entity);
359
    }
360
361
    /**
362
     * Obtains a list of contact events.
363
     *
364
     * @return \Symfony\Component\HttpFoundation\Response
365
     */
366
    public function getAllActivityAction($lead = null)
367
    {
368
        $canViewOwn    = $this->security->isGranted('lead:leads:viewown');
369
        $canViewOthers = $this->security->isGranted('lead:leads:viewother');
370
371
        if (!$canViewOthers && !$canViewOwn) {
372
            return $this->accessDenied();
373
        }
374
375
        $filters = $this->sanitizeEventFilter(InputHelper::clean($this->request->get('filters', [])));
376
        $limit   = (int) $this->request->get('limit', 25);
377
        $page    = (int) $this->request->get('page', 1);
378
        $order   = InputHelper::clean($this->request->get('order', ['timestamp', 'DESC']));
379
380
        [$events, $serializerGroups] = $this->model->getEngagements($lead, $filters, $order, $page, $limit, false);
381
382
        $view    = $this->view($events);
383
        $context = $view->getContext()->setGroups($serializerGroups);
384
        $view->setContext($context);
385
386
        return $this->handleView($view);
387
    }
388
389
    /**
390
     * Adds a DNC to the contact.
391
     *
392
     * @param $id
393
     * @param $channel
394
     *
395
     * @return \Symfony\Component\HttpFoundation\Response
396
     */
397
    public function addDncAction($id, $channel)
398
    {
399
        $entity = $this->model->getEntity((int) $id);
400
401
        if (null === $entity) {
402
            return $this->notFound();
403
        }
404
405
        if (!$this->checkEntityAccess($entity, 'edit')) {
406
            return $this->accessDenied();
407
        }
408
409
        $channelId = (int) $this->request->request->get('channelId');
410
        if ($channelId) {
411
            $channel = [$channel => $channelId];
412
        }
413
414
        // If no reason is set, default to 3 (manual)
415
        $reason = (int) $this->request->request->get('reason', DoNotContact::MANUAL);
416
417
        // If a reason is set, but it's empty or 0, show an error.
418
        if (0 === $reason) {
419
            return $this->returnError(
420
                'Invalid reason code given',
421
                Response::HTTP_BAD_REQUEST,
422
                'Reason code needs to be an integer and higher than 0.'
423
            );
424
        }
425
426
        $comments = InputHelper::clean($this->request->request->get('comments'));
427
428
        /** @var \Mautic\LeadBundle\Model\DoNotContact $doNotContact */
429
        $doNotContact = $this->get('mautic.lead.model.dnc');
430
        $doNotContact->addDncForContact($entity->getId(), $channel, $reason, $comments);
431
        $view = $this->view([$this->entityNameOne => $entity]);
432
433
        return $this->handleView($view);
434
    }
435
436
    /**
437
     * Removes a DNC from the contact.
438
     *
439
     * @param $id
440
     * @param $channel
441
     *
442
     * @return \Symfony\Component\HttpFoundation\Response
443
     */
444
    public function removeDncAction($id, $channel)
445
    {
446
        /** @var \Mautic\LeadBundle\Model\DoNotContact $doNotContact */
447
        $doNotContact = $this->get('mautic.lead.model.dnc');
448
449
        $entity = $this->model->getEntity((int) $id);
450
451
        if (null === $entity) {
452
            return $this->notFound();
453
        }
454
455
        if (!$this->checkEntityAccess($entity, 'edit')) {
456
            return $this->accessDenied();
457
        }
458
459
        $result = $doNotContact->removeDncForContact($entity->getId(), $channel);
460
        $view   = $this->view(
461
            [
462
                'recordFound'        => $result,
463
                $this->entityNameOne => $entity,
464
            ]
465
        );
466
467
        return $this->handleView($view);
468
    }
469
470
    /**
471
     * Add/Remove a UTM Tagset to/from the contact.
472
     *
473
     * @param int       $id
474
     * @param string    $method
475
     * @param array/int $data
476
     *
477
     * @return \Symfony\Component\HttpFoundation\Response
478
     */
479
    protected function applyUtmTagsAction($id, $method, $data)
480
    {
481
        $entity = $this->model->getEntity((int) $id);
482
483
        if (null === $entity) {
484
            return $this->notFound();
485
        }
486
487
        if (!$this->checkEntityAccess($entity, 'edit')) {
488
            return $this->accessDenied();
489
        }
490
491
        // calls add/remove method as appropriate
492
        $result = $this->model->$method($entity, $data);
493
494
        if (false === $result) {
495
            return $this->badRequest();
496
        }
497
498
        if ('removeUtmTags' == $method) {
499
            $view = $this->view(
500
                [
501
                    'recordFound'        => $result,
502
                    $this->entityNameOne => $entity,
503
                ]
504
            );
505
        } else {
506
            $view = $this->view([$this->entityNameOne => $entity]);
507
        }
508
509
        return $this->handleView($view);
510
    }
511
512
    /**
513
     * Adds a UTM Tagset to the contact.
514
     *
515
     * @param int $id
516
     *
517
     * @return \Symfony\Component\HttpFoundation\Response
518
     */
519
    public function addUtmTagsAction($id)
520
    {
521
        return $this->applyUtmTagsAction($id, 'addUTMTags', $this->request->request->all());
522
    }
523
524
    /**
525
     * Remove a UTM Tagset for the contact.
526
     *
527
     * @param int $id
528
     * @param int $utmid
529
     *
530
     * @return \Symfony\Component\HttpFoundation\Response
531
     */
532
    public function removeUtmTagsAction($id, $utmid)
533
    {
534
        return $this->applyUtmTagsAction($id, 'removeUtmTags', (int) $utmid);
535
    }
536
537
    /**
538
     * Creates new entity from provided params.
539
     *
540
     * @return object
541
     */
542
    public function getNewEntity(array $params)
543
    {
544
        return $this->model->checkForDuplicateContact($params);
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     */
550
    protected function prepareParametersForBinding($parameters, $entity, $action)
551
    {
552
        // Unset the tags from params to avoid a validation error
553
        if (isset($parameters['tags'])) {
554
            unset($parameters['tags']);
555
        }
556
557
        if (count($entity->getTags()) > 0) {
558
            foreach ($entity->getTags() as $tag) {
559
                $parameters['tags'][] = $tag->getId();
560
            }
561
        }
562
563
        return $parameters;
564
    }
565
566
    /**
567
     * {@inheritdoc}
568
     *
569
     * @param Lead   $entity
570
     * @param array  $parameters
571
     * @param        $form
572
     * @param string $action
573
     */
574
    protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
575
    {
576
        if ('edit' === $action) {
577
            // Merge existing duplicate contact based on unique fields if exist
578
            // new endpoints will leverage getNewEntity in order to return the correct status codes
579
            $entity = $this->model->checkForDuplicateContact($this->entityRequestParameters, $entity);
580
        }
581
582
        $manipulatorObject = $this->inBatchMode ? 'api-batch' : 'api-single';
583
584
        $entity->setManipulator(new LeadManipulator(
585
            'lead',
586
            $manipulatorObject,
587
            null,
588
            $this->get('mautic.helper.user')->getUser()->getName()
589
        ));
590
591
        if (isset($parameters['companies'])) {
592
            $this->model->modifyCompanies($entity, $parameters['companies']);
593
            unset($parameters['companies']);
594
        }
595
596
        if (isset($parameters['owner'])) {
597
            $owner = $this->getModel('user.user')->getEntity((int) $parameters['owner']);
598
            $entity->setOwner($owner);
599
            unset($parameters['owner']);
600
        }
601
602
        if (isset($parameters['stage'])) {
603
            $stage = $this->getModel('stage.stage')->getEntity((int) $parameters['stage']);
604
            $entity->setStage($stage);
605
            unset($parameters['stage']);
606
        }
607
608
        if (isset($this->entityRequestParameters['tags'])) {
609
            $this->model->modifyTags($entity, $this->entityRequestParameters['tags'], null, false);
610
        }
611
612
        //Since the request can be from 3rd party, check for an IP address if included
613
        if (isset($this->entityRequestParameters['ipAddress'])) {
614
            $ipAddress = $this->get('mautic.helper.ip_lookup')->getIpAddress($this->entityRequestParameters['ipAddress']);
615
616
            if (!$entity->getIpAddresses()->contains($ipAddress)) {
617
                $entity->addIpAddress($ipAddress);
618
            }
619
620
            unset($this->entityRequestParameters['ipAddress']);
621
        }
622
623
        // Check for lastActive date
624
        if (isset($this->entityRequestParameters['lastActive'])) {
625
            $lastActive = new DateTimeHelper($this->entityRequestParameters['lastActive']);
626
            $entity->setLastActive($lastActive->getDateTime());
627
            unset($this->entityRequestParameters['lastActive']);
628
        }
629
630
        // Batch DNC settings
631
        if (!empty($parameters['doNotContact']) && is_array($parameters['doNotContact'])) {
632
            foreach ($parameters['doNotContact'] as $dnc) {
633
                $channel  = !empty($dnc['channel']) ? $dnc['channel'] : 'email';
634
                $comments = !empty($dnc['comments']) ? $dnc['comments'] : '';
635
636
                $reason = (int) ArrayHelper::getValue('reason', $dnc, DoNotContact::MANUAL);
637
638
                /** @var DoNotContactModel $doNotContact */
639
                $doNotContact = $this->get('mautic.lead.model.dnc');
640
641
                if (DoNotContact::IS_CONTACTABLE === $reason) {
642
                    if (!empty($entity->getId())) {
643
                        // Remove DNC record
644
                        $doNotContact->removeDncForContact($entity->getId(), $channel, false);
645
                    }
646
                } elseif (empty($entity->getId())) {
647
                    // Contact doesn't exist yet. Directly create a DNC record on the entity.
648
                    $doNotContact->createDncRecord($entity, $channel, $reason, $comments);
649
                } else {
650
                    // Add DNC record to existing contact
651
                    $doNotContact->addDncForContact($entity->getId(), $channel, $reason, $comments, false);
652
                }
653
            }
654
            unset($parameters['doNotContact']);
655
        }
656
657
        if (!empty($parameters['frequencyRules'])) {
658
            $viewParameters = [];
659
            $data           = $this->getFrequencyRuleFormData($entity, null, null, false, $parameters['frequencyRules']);
660
661
            if (!$frequencyForm = $this->getFrequencyRuleForm($entity, $viewParameters, $data)) {
662
                $formErrors = $this->getFormErrorMessages($frequencyForm);
663
                $msg        = $this->getFormErrorMessage($formErrors);
664
665
                if (!$msg) {
666
                    $msg = $this->translator->trans('mautic.core.error.badrequest', [], 'flashes');
667
                }
668
669
                return $this->returnError($msg, Response::HTTP_BAD_REQUEST, $formErrors);
670
            }
671
672
            unset($parameters['frequencyRules']);
673
        }
674
675
        $isPostOrPatch = 'POST' === $this->request->getMethod() || 'PATCH' === $this->request->getMethod();
676
        $this->setCustomFieldValues($entity, $form, $parameters, $isPostOrPatch);
0 ignored issues
show
It seems like $entity can also be of type array; however, parameter $entity of Mautic\LeadBundle\Contro...:setCustomFieldValues() does only seem to accept Mautic\LeadBundle\Entity...\LeadBundle\Entity\Lead, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

676
        $this->setCustomFieldValues(/** @scrutinizer ignore-type */ $entity, $form, $parameters, $isPostOrPatch);
Loading history...
677
    }
678
679
    /**
680
     * Helper method to be used in FrequencyRuleTrait.
681
     *
682
     * @param Form $form
683
     *
684
     * @return bool
685
     */
686
    protected function isFormCancelled($form = null)
687
    {
688
        return false;
689
    }
690
691
    /**
692
     * Helper method to be used in FrequencyRuleTrait.
693
     *
694
     * @param array $data
695
     *
696
     * @return bool
697
     */
698
    protected function isFormValid(Form $form, array $data = null)
699
    {
700
        $form->submit($data, 'PATCH' !== $this->request->getMethod());
701
702
        return $form->isValid();
703
    }
704
}
705