Issues (3627)

app/bundles/LeadBundle/Model/ListModel.php (3 issues)

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\Model;
13
14
use Doctrine\DBAL\DBALException;
15
use Mautic\CoreBundle\Helper\Chart\BarChart;
16
use Mautic\CoreBundle\Helper\Chart\ChartQuery;
17
use Mautic\CoreBundle\Helper\Chart\LineChart;
18
use Mautic\CoreBundle\Helper\Chart\PieChart;
19
use Mautic\CoreBundle\Helper\CoreParametersHelper;
20
use Mautic\CoreBundle\Helper\DateTimeHelper;
21
use Mautic\CoreBundle\Helper\ProgressBarHelper;
22
use Mautic\CoreBundle\Model\FormModel;
23
use Mautic\LeadBundle\Entity\Lead;
24
use Mautic\LeadBundle\Entity\LeadField;
25
use Mautic\LeadBundle\Entity\LeadList;
26
use Mautic\LeadBundle\Entity\LeadListRepository;
27
use Mautic\LeadBundle\Entity\ListLead;
28
use Mautic\LeadBundle\Entity\ListLeadRepository;
29
use Mautic\LeadBundle\Entity\OperatorListTrait;
30
use Mautic\LeadBundle\Event\LeadListEvent;
31
use Mautic\LeadBundle\Event\LeadListFiltersChoicesEvent;
32
use Mautic\LeadBundle\Event\ListChangeEvent;
33
use Mautic\LeadBundle\Event\ListPreProcessListEvent;
34
use Mautic\LeadBundle\Form\Type\ListType;
35
use Mautic\LeadBundle\Helper\FormFieldHelper;
36
use Mautic\LeadBundle\LeadEvents;
37
use Mautic\LeadBundle\Segment\ContactSegmentService;
38
use Mautic\LeadBundle\Segment\Exception\FieldNotFoundException;
39
use Mautic\LeadBundle\Segment\Exception\SegmentNotFoundException;
40
use Mautic\LeadBundle\Segment\Stat\ChartQuery\SegmentContactsLineChartQuery;
41
use Mautic\LeadBundle\Segment\Stat\SegmentChartQueryFactory;
42
use Symfony\Component\Console\Output\OutputInterface;
43
use Symfony\Component\EventDispatcher\Event;
44
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
45
use Symfony\Component\PropertyAccess\PropertyAccessor;
46
47
class ListModel extends FormModel
48
{
49
    use OperatorListTrait;
50
51
    /**
52
     * @var CoreParametersHelper
53
     */
54
    protected $coreParametersHelper;
55
56
    /**
57
     * @var ContactSegmentService
58
     */
59
    private $leadSegmentService;
60
61
    /**
62
     * @var SegmentChartQueryFactory
63
     */
64
    private $segmentChartQueryFactory;
65
66
    public function __construct(CoreParametersHelper $coreParametersHelper, ContactSegmentService $leadSegment, SegmentChartQueryFactory $segmentChartQueryFactory)
67
    {
68
        $this->coreParametersHelper     = $coreParametersHelper;
69
        $this->leadSegmentService       = $leadSegment;
70
        $this->segmentChartQueryFactory = $segmentChartQueryFactory;
71
    }
72
73
    /**
74
     * Used by addLead and removeLead functions.
75
     *
76
     * @var array
77
     */
78
    private $leadChangeLists = [];
79
80
    /**
81
     * {@inheritdoc}
82
     *
83
     * @return LeadListRepository
84
     */
85
    public function getRepository()
86
    {
87
        /** @var LeadListRepository $repo */
88
        $repo = $this->em->getRepository(LeadList::class);
89
90
        $repo->setDispatcher($this->dispatcher);
91
        $repo->setTranslator($this->translator);
92
93
        return $repo;
94
    }
95
96
    /**
97
     * Returns the repository for the table that houses the leads associated with a list.
98
     *
99
     * @return ListLeadRepository
100
     */
101
    public function getListLeadRepository()
102
    {
103
        return $this->em->getRepository(ListLead::class);
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     *
109
     * @return string
110
     */
111
    public function getPermissionBase()
112
    {
113
        return 'lead:lists';
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     *
119
     * @param      $entity
120
     * @param bool $unlock
121
     *
122
     * @return mixed|void
123
     *
124
     * @throws DBALException
125
     */
126
    public function saveEntity($entity, $unlock = true)
127
    {
128
        $isNew = ($entity->getId()) ? false : true;
129
130
        //set some defaults
131
        $this->setTimestamps($entity, $isNew, $unlock);
132
133
        $alias = $entity->getAlias();
134
        if (empty($alias)) {
135
            $alias = $entity->getName();
136
        }
137
        $alias = $this->cleanAlias($alias, '', false, '-');
138
139
        //make sure alias is not already taken
140
        $repo      = $this->getRepository();
141
        $testAlias = $alias;
142
        $existing  = $repo->getLists(null, $testAlias, $entity->getId());
143
        $count     = count($existing);
144
        $aliasTag  = $count;
145
146
        while ($count) {
147
            $testAlias = $alias.$aliasTag;
148
            $existing  = $repo->getLists(null, $testAlias, $entity->getId());
149
            $count     = count($existing);
150
            ++$aliasTag;
151
        }
152
        if ($testAlias != $alias) {
153
            $alias = $testAlias;
154
        }
155
        $entity->setAlias($alias);
156
157
        $publicName = $entity->getPublicName();
158
        if (empty($publicName)) {
159
            $entity->setPublicName($entity->getName());
160
        }
161
162
        $event = $this->dispatchEvent('pre_save', $entity, $isNew);
163
        $repo->saveEntity($entity);
164
        $this->dispatchEvent('post_save', $entity, $isNew, $event);
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     *
170
     * @param       $entity
171
     * @param       $formFactory
172
     * @param null  $action
173
     * @param array $options
174
     *
175
     * @return mixed
176
     *
177
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
178
     */
179
    public function createForm($entity, $formFactory, $action = null, $options = [])
180
    {
181
        if (!$entity instanceof LeadList) {
182
            throw new MethodNotAllowedHttpException(['LeadList'], 'Entity must be of class LeadList()');
183
        }
184
185
        if (!empty($action)) {
186
            $options['action'] = $action;
187
        }
188
189
        return $formFactory->create(ListType::class, $entity, $options);
190
    }
191
192
    /**
193
     * Get a specific entity or generate a new one if id is empty.
194
     *
195
     * @param $id
196
     *
197
     * @return object|null
198
     */
199
    public function getEntity($id = null)
200
    {
201
        if (null === $id) {
202
            return new LeadList();
203
        }
204
205
        return parent::getEntity($id);
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     *
211
     * @param $action
212
     * @param $event
213
     * @param $entity
214
     * @param $isNew
215
     *
216
     * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
217
     */
218
    protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null)
219
    {
220
        if (!$entity instanceof LeadList) {
221
            throw new MethodNotAllowedHttpException(['LeadList'], 'Entity must be of class LeadList()');
222
        }
223
224
        switch ($action) {
225
            case 'pre_save':
226
                $name = LeadEvents::LIST_PRE_SAVE;
227
                break;
228
            case 'post_save':
229
                $name = LeadEvents::LIST_POST_SAVE;
230
                break;
231
            case 'pre_delete':
232
                $name = LeadEvents::LIST_PRE_DELETE;
233
                break;
234
            case 'post_delete':
235
                $name = LeadEvents::LIST_POST_DELETE;
236
                break;
237
            default:
238
                return null;
239
        }
240
241
        if ($this->dispatcher->hasListeners($name)) {
242
            if (empty($event)) {
243
                $event = new LeadListEvent($entity, $isNew);
244
                $event->setEntityManager($this->em);
245
            }
246
            $this->dispatcher->dispatch($name, $event);
247
248
            return $event;
249
        } else {
250
            return null;
251
        }
252
    }
253
254
    /**
255
     * Get a list of field choices for filters.
256
     *
257
     * @return array
258
     */
259
    public function getChoiceFields()
260
    {
261
        //field choices
262
        $choices['lead'] = [
263
            'date_added' => [
264
                'label'      => $this->translator->trans('mautic.core.date.added'),
265
                'properties' => ['type' => 'date'],
266
                'operators'  => $this->getOperatorsForFieldType('default'),
267
                'object'     => 'lead',
268
            ],
269
            'date_identified' => [
270
                'label'      => $this->translator->trans('mautic.lead.list.filter.date_identified'),
271
                'properties' => ['type' => 'date'],
272
                'operators'  => $this->getOperatorsForFieldType('default'),
273
                'object'     => 'lead',
274
            ],
275
            'last_active' => [
276
                'label'      => $this->translator->trans('mautic.lead.list.filter.last_active'),
277
                'properties' => ['type' => 'datetime'],
278
                'operators'  => $this->getOperatorsForFieldType('default'),
279
                'object'     => 'lead',
280
            ],
281
            'date_modified' => [
282
                'label'      => $this->translator->trans('mautic.lead.list.filter.date_modified'),
283
                'properties' => ['type' => 'datetime'],
284
                'operators'  => $this->getOperatorsForFieldType('default'),
285
                'object'     => 'lead',
286
            ],
287
            'owner_id' => [
288
                'label'      => $this->translator->trans('mautic.lead.list.filter.owner'),
289
                'properties' => [
290
                    'type'     => 'lookup_id',
291
                    'callback' => 'activateSegmentFilterTypeahead',
292
                ],
293
                'operators' => $this->getOperatorsForFieldType('lookup_id'),
294
                'object'    => 'lead',
295
            ],
296
            'points' => [
297
                'label'      => $this->translator->trans('mautic.lead.lead.event.points'),
298
                'properties' => ['type' => 'number'],
299
                'operators'  => $this->getOperatorsForFieldType('default'),
300
                'object'     => 'lead',
301
            ],
302
            'leadlist' => [
303
                'label'      => $this->translator->trans('mautic.lead.list.filter.lists'),
304
                'properties' => [
305
                    'type' => 'leadlist',
306
                ],
307
                'operators' => $this->getOperatorsForFieldType('multiselect'),
308
                'object'    => 'lead',
309
            ],
310
            'campaign' => [
311
                'label'      => $this->translator->trans('mautic.lead.list.filter.campaign'),
312
                'properties' => [
313
                    'type' => 'campaign',
314
                ],
315
                'operators' => $this->getOperatorsForFieldType('multiselect'),
316
                'object'    => 'lead',
317
            ],
318
            'lead_asset_download' => [
319
                'label'      => $this->translator->trans('mautic.lead.list.filter.lead_asset_download'),
320
                'properties' => ['type' => 'assets'],
321
                'operators'  => $this->getOperatorsForFieldType('multiselect'),
322
                'object'     => 'lead',
323
            ],
324
            'lead_email_received' => [
325
                'label'      => $this->translator->trans('mautic.lead.list.filter.lead_email_received'),
326
                'properties' => [
327
                    'type' => 'lead_email_received',
328
                ],
329
                'operators' => $this->getOperatorsForFieldType(
330
                    [
331
                        'include' => [
332
                            'in',
333
                            '!in',
334
                        ],
335
                    ]
336
                ),
337
                'object' => 'lead',
338
            ],
339
            'lead_email_sent' => [
340
                'label'      => $this->translator->trans('mautic.lead.list.filter.lead_email_sent'),
341
                'properties' => [
342
                    'type' => 'lead_email_received',
343
                ],
344
                'operators' => $this->getOperatorsForFieldType(
345
                    [
346
                        'include' => [
347
                            'in',
348
                            '!in',
349
                        ],
350
                    ]
351
                ),
352
                'object' => 'lead',
353
            ],
354
            'lead_email_sent_date' => [
355
                'label'      => $this->translator->trans('mautic.lead.list.filter.lead_email_sent_date'),
356
                'properties' => ['type' => 'datetime'],
357
                'operators'  => $this->getOperatorsForFieldType(
358
                    [
359
                        'include' => [
360
                            '=',
361
                            '!=',
362
                            'gt',
363
                            'lt',
364
                            'gte',
365
                            'lte',
366
                        ],
367
                    ]
368
                ),
369
                'object' => 'lead',
370
            ],
371
            'lead_email_read_date' => [
372
                'label'      => $this->translator->trans('mautic.lead.list.filter.lead_email_read_date'),
373
                'properties' => ['type' => 'datetime'],
374
                'operators'  => $this->getOperatorsForFieldType(
375
                    [
376
                        'include' => [
377
                            '=',
378
                            '!=',
379
                            'gt',
380
                            'lt',
381
                            'gte',
382
                            'lte',
383
                        ],
384
                    ]
385
                ),
386
                'object' => 'lead',
387
            ],
388
            'lead_email_read_count' => [
389
                'label'      => $this->translator->trans('mautic.lead.list.filter.lead_email_read_count'),
390
                'properties' => ['type' => 'number'],
391
                'operators'  => $this->getOperatorsForFieldType(
392
                    [
393
                        'include' => [
394
                            '=',
395
                            'gt',
396
                            'gte',
397
                            'lt',
398
                            'lte',
399
                        ],
400
                    ]
401
                ),
402
                'object' => 'lead',
403
            ],
404
            'tags' => [
405
                'label'      => $this->translator->trans('mautic.lead.list.filter.tags'),
406
                'properties' => [
407
                    'type' => 'tags',
408
                ],
409
                'operators' => $this->getOperatorsForFieldType('multiselect'),
410
                'object'    => 'lead',
411
            ],
412
            'device_type' => [
413
                'label'      => $this->translator->trans('mautic.lead.list.filter.device_type'),
414
                'properties' => [
415
                    'type' => 'device_type',
416
                ],
417
                'operators' => $this->getOperatorsForFieldType('multiselect'),
418
                'object'    => 'lead',
419
            ],
420
            'device_brand' => [
421
                'label'      => $this->translator->trans('mautic.lead.list.filter.device_brand'),
422
                'properties' => [
423
                    'type' => 'device_brand',
424
                ],
425
                'operators' => $this->getOperatorsForFieldType('multiselect'),
426
                'object'    => 'lead',
427
            ],
428
            'device_os' => [
429
                'label'      => $this->translator->trans('mautic.lead.list.filter.device_os'),
430
                'properties' => [
431
                    'type' => 'device_os',
432
                ],
433
                'operators' => $this->getOperatorsForFieldType('multiselect'),
434
                'object'    => 'lead',
435
            ],
436
            'device_model' => [
437
                'label'      => $this->translator->trans('mautic.lead.list.filter.device_model'),
438
                'properties' => [
439
                    'type' => 'text',
440
                ],
441
                'operators' => $this->getOperatorsForFieldType(
442
                    [
443
                        'include' => [
444
                            '=',
445
                            'like',
446
                            'regexp',
447
                        ],
448
                    ]
449
                ),
450
                'object' => 'lead',
451
            ],
452
            'dnc_bounced' => [
453
                'label'      => $this->translator->trans('mautic.lead.list.filter.dnc_bounced'),
454
                'properties' => [
455
                    'type' => 'boolean',
456
                    'list' => [
457
                        0 => $this->translator->trans('mautic.core.form.no'),
458
                        1 => $this->translator->trans('mautic.core.form.yes'),
459
                    ],
460
                ],
461
                'operators' => $this->getOperatorsForFieldType('bool'),
462
                'object'    => 'lead',
463
            ],
464
            'dnc_unsubscribed' => [
465
                'label'      => $this->translator->trans('mautic.lead.list.filter.dnc_unsubscribed'),
466
                'properties' => [
467
                    'type' => 'boolean',
468
                    'list' => [
469
                        0 => $this->translator->trans('mautic.core.form.no'),
470
                        1 => $this->translator->trans('mautic.core.form.yes'),
471
                    ],
472
                ],
473
                'operators' => $this->getOperatorsForFieldType('bool'),
474
                'object'    => 'lead',
475
            ],
476
            'dnc_manual_email' => [
477
                'label'      => $this->translator->trans('mautic.lead.list.filter.dnc_manual_email'),
478
                'properties' => [
479
                    'type' => 'boolean',
480
                    'list' => [
481
                        0 => $this->translator->trans('mautic.core.form.no'),
482
                        1 => $this->translator->trans('mautic.core.form.yes'),
483
                    ],
484
                ],
485
                'operators' => $this->getOperatorsForFieldType('bool'),
486
                'object'    => 'lead',
487
            ],
488
            'dnc_bounced_sms' => [
489
                'label'      => $this->translator->trans('mautic.lead.list.filter.dnc_bounced_sms'),
490
                'properties' => [
491
                    'type' => 'boolean',
492
                    'list' => [
493
                        0 => $this->translator->trans('mautic.core.form.no'),
494
                        1 => $this->translator->trans('mautic.core.form.yes'),
495
                    ],
496
                ],
497
                'operators' => $this->getOperatorsForFieldType('bool'),
498
                'object'    => 'lead',
499
            ],
500
            'dnc_unsubscribed_sms' => [
501
                'label'      => $this->translator->trans('mautic.lead.list.filter.dnc_unsubscribed_sms'),
502
                'properties' => [
503
                    'type' => 'boolean',
504
                    'list' => [
505
                        0 => $this->translator->trans('mautic.core.form.no'),
506
                        1 => $this->translator->trans('mautic.core.form.yes'),
507
                    ],
508
                ],
509
                'operators' => $this->getOperatorsForFieldType('bool'),
510
                'object'    => 'lead',
511
            ],
512
            'hit_url' => [
513
                'label'      => $this->translator->trans('mautic.lead.list.filter.visited_url'),
514
                'properties' => [
515
                    'type' => 'text',
516
                ],
517
                'operators' => $this->getOperatorsForFieldType(
518
                    [
519
                        'include' => [
520
                            '=',
521
                            '!=',
522
                            'like',
523
                            '!like',
524
                            'regexp',
525
                            '!regexp',
526
                            'startsWith',
527
                            'endsWith',
528
                            'contains',
529
                        ],
530
                    ]
531
                ),
532
                'object' => 'lead',
533
            ],
534
            'hit_url_date' => [
535
                'label'      => $this->translator->trans('mautic.lead.list.filter.visited_url_date'),
536
                'properties' => ['type' => 'datetime'],
537
                'operators'  => $this->getOperatorsForFieldType(
538
                    [
539
                        'include' => [
540
                            '=',
541
                            '!=',
542
                            'gt',
543
                            'lt',
544
                            'gte',
545
                            'lte',
546
                        ],
547
                    ]
548
                ),
549
                'object' => 'lead',
550
            ],
551
            'hit_url_count' => [
552
                'label'      => $this->translator->trans('mautic.lead.list.filter.visited_url_count'),
553
                'properties' => ['type' => 'number'],
554
                'operators'  => $this->getOperatorsForFieldType(
555
                    [
556
                        'include' => [
557
                            '=',
558
                            'gt',
559
                            'gte',
560
                            'lt',
561
                            'lte',
562
                        ],
563
                    ]
564
                ),
565
                'object' => 'lead',
566
            ],
567
            'sessions' => [
568
                'label'      => $this->translator->trans('mautic.lead.list.filter.session'),
569
                'properties' => ['type' => 'number'],
570
                'operators'  => $this->getOperatorsForFieldType(
571
                    [
572
                        'include' => [
573
                            '=',
574
                            'gt',
575
                            'gte',
576
                            'lt',
577
                            'lte',
578
                        ],
579
                    ]
580
                ),
581
                'object' => 'lead',
582
            ],
583
            'referer' => [
584
                'label'      => $this->translator->trans('mautic.lead.list.filter.referer'),
585
                'properties' => [
586
                    'type' => 'text',
587
                ],
588
                'operators' => $this->getOperatorsForFieldType(
589
                    [
590
                        'include' => [
591
                            '=',
592
                            '!=',
593
                            'like',
594
                            '!like',
595
                            'regexp',
596
                            '!regexp',
597
                            'startsWith',
598
                            'endsWith',
599
                            'contains',
600
                        ],
601
                    ]
602
                ),
603
                'object' => 'lead',
604
            ],
605
            'url_title' => [
606
                'label'      => $this->translator->trans('mautic.lead.list.filter.url_title'),
607
                'properties' => [
608
                    'type' => 'text',
609
                ],
610
                'operators' => $this->getOperatorsForFieldType(
611
                    [
612
                        'include' => [
613
                            '=',
614
                            '!=',
615
                            'like',
616
                            '!like',
617
                            'regexp',
618
                            '!regexp',
619
                            'startsWith',
620
                            'endsWith',
621
                            'contains',
622
                        ],
623
                    ]
624
                ),
625
                'object' => 'lead',
626
            ],
627
            'source' => [
628
                'label'      => $this->translator->trans('mautic.lead.list.filter.source'),
629
                'properties' => [
630
                    'type' => 'text',
631
                ],
632
                'operators' => $this->getOperatorsForFieldType(
633
                    [
634
                        'include' => [
635
                            '=',
636
                            '!=',
637
                            'like',
638
                            '!like',
639
                            'regexp',
640
                            '!regexp',
641
                            'startsWith',
642
                            'endsWith',
643
                            'contains',
644
                        ],
645
                    ]
646
                ),
647
                'object' => 'lead',
648
            ],
649
            'source_id' => [
650
                'label'      => $this->translator->trans('mautic.lead.list.filter.source.id'),
651
                'properties' => [
652
                    'type' => 'number',
653
                ],
654
                'operators' => $this->getOperatorsForFieldType('default'),
655
                'object'    => 'lead',
656
            ],
657
            'notification' => [
658
                'label'      => $this->translator->trans('mautic.lead.list.filter.notification'),
659
                'properties' => [
660
                    'type' => 'boolean',
661
                    'list' => [
662
                        0 => $this->translator->trans('mautic.core.form.no'),
663
                        1 => $this->translator->trans('mautic.core.form.yes'),
664
                    ],
665
                ],
666
                'operators' => $this->getOperatorsForFieldType('bool'),
667
                'object'    => 'lead',
668
            ],
669
            'page_id' => [
670
                'label'      => $this->translator->trans('mautic.lead.list.filter.page_id'),
671
                'properties' => [
672
                    'type' => 'boolean',
673
                    'list' => [
674
                        0 => $this->translator->trans('mautic.core.form.no'),
675
                        1 => $this->translator->trans('mautic.core.form.yes'),
676
                    ],
677
                ],
678
                'operators' => $this->getOperatorsForFieldType('bool'),
679
                'object'    => 'lead',
680
            ],
681
            'email_id' => [
682
                'label'      => $this->translator->trans('mautic.lead.list.filter.email_id'),
683
                'properties' => [
684
                    'type' => 'boolean',
685
                    'list' => [
686
                        0 => $this->translator->trans('mautic.core.form.no'),
687
                        1 => $this->translator->trans('mautic.core.form.yes'),
688
                    ],
689
                ],
690
                'operators' => $this->getOperatorsForFieldType('bool'),
691
                'object'    => 'lead',
692
            ],
693
            'redirect_id' => [
694
                'label'      => $this->translator->trans('mautic.lead.list.filter.redirect_id'),
695
                'properties' => [
696
                    'type' => 'boolean',
697
                    'list' => [
698
                        0 => $this->translator->trans('mautic.core.form.no'),
699
                        1 => $this->translator->trans('mautic.core.form.yes'),
700
                    ],
701
                ],
702
                'operators' => $this->getOperatorsForFieldType('bool'),
703
                'object'    => 'lead',
704
            ],
705
            'stage' => [
706
                'label'      => $this->translator->trans('mautic.lead.lead.field.stage'),
707
                'properties' => [
708
                    'type' => 'stage',
709
                ],
710
                'operators' => $this->getOperatorsForFieldType(
711
                    [
712
                        'include' => [
713
                            '=',
714
                            '!=',
715
                            'empty',
716
                            '!empty',
717
                        ],
718
                    ]
719
                ),
720
                'object' => 'lead',
721
            ],
722
            'globalcategory' => [
723
                'label'      => $this->translator->trans('mautic.lead.list.filter.categories'),
724
                'properties' => [
725
                    'type' => 'globalcategory',
726
                ],
727
                'operators' => $this->getOperatorsForFieldType('multiselect'),
728
                'object'    => 'lead',
729
            ],
730
            'utm_campaign' => [
731
                'label'      => $this->translator->trans('mautic.lead.list.filter.utmcampaign'),
732
                'properties' => [
733
                    'type' => 'text',
734
                ],
735
                'operators' => $this->getOperatorsForFieldType('default'),
736
                'object'    => 'lead',
737
            ],
738
            'utm_content' => [
739
                'label'      => $this->translator->trans('mautic.lead.list.filter.utmcontent'),
740
                'properties' => [
741
                    'type' => 'text',
742
                ],
743
                'operators' => $this->getOperatorsForFieldType('default'),
744
                'object'    => 'lead',
745
            ],
746
            'utm_medium' => [
747
                'label'      => $this->translator->trans('mautic.lead.list.filter.utmmedium'),
748
                'properties' => [
749
                    'type' => 'text',
750
                ],
751
                'operators' => $this->getOperatorsForFieldType('default'),
752
                'object'    => 'lead',
753
            ],
754
            'utm_source' => [
755
                'label'      => $this->translator->trans('mautic.lead.list.filter.utmsource'),
756
                'properties' => [
757
                    'type' => 'text',
758
                ],
759
                'operators' => $this->getOperatorsForFieldType('default'),
760
                'object'    => 'lead',
761
            ],
762
            'utm_term' => [
763
                'label'      => $this->translator->trans('mautic.lead.list.filter.utmterm'),
764
                'properties' => [
765
                    'type' => 'text',
766
                ],
767
                'operators' => $this->getOperatorsForFieldType('default'),
768
                'object'    => 'lead',
769
            ],
770
        ];
771
772
        // Add custom choices
773
        if ($this->dispatcher->hasListeners(LeadEvents::LIST_FILTERS_CHOICES_ON_GENERATE)) {
774
            $event = new LeadListFiltersChoicesEvent($choices, $this->getOperatorsForFieldType(), $this->translator);
775
            $this->dispatcher->dispatch(LeadEvents::LIST_FILTERS_CHOICES_ON_GENERATE, $event);
776
            $choices = $event->getChoices();
777
        }
778
779
        //get list of custom fields
780
        $fields = $this->em->getRepository(LeadField::class)->getEntities(
781
            [
782
                'filter' => [
783
                    'where'         => [
784
                        [
785
                            'expr' => 'eq',
786
                            'col'  => 'f.isListable',
787
                            'val'  => true,
788
                        ],
789
                        [
790
                            'expr' => 'eq',
791
                            'col'  => 'f.isPublished',
792
                            'val'  => true,
793
                        ],
794
                    ],
795
                ],
796
                'orderBy' => 'f.object',
797
            ]
798
        );
799
        foreach ($fields as $field) {
800
            $type               = $field->getType();
801
            $properties         = $field->getProperties();
802
            $properties['type'] = $type;
803
            if (in_array($type, ['select', 'multiselect', 'boolean'])) {
804
                if ('boolean' == $type) {
805
                    //create a lookup list with ID
806
                    $properties['list'] = [
807
                        0 => $properties['no'],
808
                        1 => $properties['yes'],
809
                    ];
810
                } else {
811
                    $properties['callback'] = 'activateLeadFieldTypeahead';
812
                    $properties['list']     = (isset($properties['list'])) ? FormFieldHelper::formatList(
813
                        FormFieldHelper::FORMAT_ARRAY,
814
                        FormFieldHelper::parseList($properties['list'])
815
                    ) : '';
816
                }
817
            }
818
            $choices[$field->getObject()][$field->getAlias()] = [
819
                'label'      => $field->getLabel(),
820
                'properties' => $properties,
821
                'object'     => $field->getObject(),
822
            ];
823
824
            $choices[$field->getObject()][$field->getAlias()]['operators'] = $this->getOperatorsForFieldType($type);
825
        }
826
827
        foreach ($choices as $key => $choice) {
828
            $cmp = function ($a, $b) {
829
                return strcmp($a['label'], $b['label']);
830
            };
831
            uasort($choice, $cmp);
832
            $choices[$key] = $choice;
833
        }
834
835
        return $choices;
836
    }
837
838
    /**
839
     * @param string $alias
840
     *
841
     * @return array
842
     */
843
    public function getUserLists($alias = '')
844
    {
845
        $user = !$this->security->isGranted('lead:lists:viewother') ? $this->userHelper->getUser() : null;
846
847
        return $this->em->getRepository(LeadList::class)->getLists($user, $alias);
848
    }
849
850
    /**
851
     * Get a list of global lead lists.
852
     *
853
     * @return mixed
854
     */
855
    public function getGlobalLists()
856
    {
857
        return $this->em->getRepository(LeadList::class)->getGlobalLists();
858
    }
859
860
    /**
861
     * Get a list of preference center lead lists.
862
     *
863
     * @return mixed
864
     */
865
    public function getPreferenceCenterLists()
866
    {
867
        return $this->em->getRepository(LeadList::class)->getPreferenceCenterList();
868
    }
869
870
    /**
871
     * @return array
872
     *
873
     * @throws \Exception
874
     */
875
    public function getVersionNew(LeadList $entity)
876
    {
877
        $dtHelper      = new DateTimeHelper();
878
        $batchLimiters = [
879
            'dateTime' => $dtHelper->toUtcString(),
880
        ];
881
882
        return $this->leadSegmentService->getNewLeadListLeadsCount($entity, $batchLimiters);
883
    }
884
885
    /**
886
     * @return mixed
887
     */
888
    public function getVersionOld(LeadList $entity)
889
    {
890
        $batchLimiters = [
891
            'dateTime' => (new DateTimeHelper())->toUtcString(),
892
        ];
893
894
        $newLeadsCount = $this->leadSegmentService->getNewLeadListLeadsCount(
895
            $entity,
896
            $batchLimiters
897
        );
898
899
        return array_shift($newLeadsCount);
900
    }
901
902
    /**
903
     * @param int  $limit
904
     * @param bool $maxLeads
905
     *
906
     * @return int
907
     *
908
     * @throws \Doctrine\ORM\ORMException
909
     * @throws \Exception
910
     */
911
    public function rebuildListLeads(LeadList $leadList, $limit = 100, $maxLeads = false, OutputInterface $output = null)
912
    {
913
        defined('MAUTIC_REBUILDING_LEAD_LISTS') or define('MAUTIC_REBUILDING_LEAD_LISTS', 1);
914
915
        $dtHelper = new DateTimeHelper();
916
917
        $batchLimiters = ['dateTime' => $dtHelper->toUtcString()];
918
        $list          = ['id' => $leadList->getId(), 'filters' => $leadList->getFilters()];
919
920
        $this->dispatcher->dispatch(
921
            LeadEvents::LIST_PRE_PROCESS_LIST, new ListPreProcessListEvent($list, false)
922
        );
923
924
        try {
925
            // Get a count of leads to add
926
            $newLeadsCount = $this->leadSegmentService->getNewLeadListLeadsCount($leadList, $batchLimiters);
927
        } catch (FieldNotFoundException $e) {
928
            // A field from filter does not exist anymore. Do not rebuild.
929
            return 0;
930
        } catch (SegmentNotFoundException $e) {
931
            // A segment from filter does not exist anymore. Do not rebuild.
932
            return 0;
933
        }
934
935
        // Ensure the same list is used each batch <- would love to know how
936
        $batchLimiters['maxId'] = (int) $newLeadsCount[$leadList->getId()]['maxId'];
937
938
        // Number of total leads to process
939
        $leadCount = (int) $newLeadsCount[$leadList->getId()]['count'];
940
941
        $this->logger->info('Segment QB - No new leads for segment found');
942
943
        if ($output) {
944
            $output->writeln($this->translator->trans('mautic.lead.list.rebuild.to_be_added', ['%leads%' => $leadCount, '%batch%' => $limit]));
945
        }
946
947
        // Handle by batches
948
        $start = $lastRoundPercentage = $leadsProcessed = 0;
0 ignored issues
show
The assignment to $lastRoundPercentage is dead and can be removed.
Loading history...
949
950
        // Try to save some memory
951
        gc_enable();
952
953
        if ($leadCount) {
954
            $maxCount = ($maxLeads) ? $maxLeads : $leadCount;
955
956
            if ($output) {
957
                $progress = ProgressBarHelper::init($output, $maxCount);
0 ignored issues
show
It seems like $maxCount can also be of type true; however, parameter $maxCount of Mautic\CoreBundle\Helper\ProgressBarHelper::init() does only seem to accept integer, 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

957
                $progress = ProgressBarHelper::init($output, /** @scrutinizer ignore-type */ $maxCount);
Loading history...
958
                $progress->start();
959
            }
960
961
            // Add leads
962
            while ($start < $leadCount) {
963
                // Keep CPU down for large lists; sleep per $limit batch
964
                $this->batchSleep();
965
966
                $this->logger->debug(sprintf('Segment QB - Fetching new leads for segment [%d] %s', $leadList->getId(), $leadList->getName()));
967
                $newLeadList = $this->leadSegmentService->getNewLeadListLeads($leadList, $batchLimiters, $limit);
968
969
                if (empty($newLeadList[$leadList->getId()])) {
970
                    // Somehow ran out of leads so break out
971
                    break;
972
                }
973
974
                $this->logger->debug(sprintf('Segment QB - Adding %d new leads to segment [%d] %s', count($newLeadList[$leadList->getId()]), $leadList->getId(), $leadList->getName()));
975
                foreach ($newLeadList[$leadList->getId()] as $l) {
976
                    $this->logger->debug(sprintf('Segment QB - Adding lead #%s to segment [%d] %s', $l['id'], $leadList->getId(), $leadList->getName()));
977
978
                    $this->addLead($l, $leadList, false, true, -1, $dtHelper->getLocalDateTime());
979
980
                    ++$leadsProcessed;
981
                    if ($output && $leadsProcessed < $maxCount) {
982
                        $progress->setProgress($leadsProcessed);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $progress does not seem to be defined for all execution paths leading up to this point.
Loading history...
983
                    }
984
985
                    if ($maxLeads && $leadsProcessed >= $maxLeads) {
986
                        break;
987
                    }
988
                }
989
990
                $this->logger->info(sprintf('Segment QB - Added %d new leads to segment [%d] %s', count($newLeadList[$leadList->getId()]), $leadList->getId(), $leadList->getName()));
991
992
                $start += $limit;
993
994
                // Dispatch batch event
995
                if (count($newLeadList[$leadList->getId()]) && $this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_BATCH_CHANGE)) {
996
                    $this->dispatcher->dispatch(
997
                        LeadEvents::LEAD_LIST_BATCH_CHANGE,
998
                        new ListChangeEvent($newLeadList[$leadList->getId()], $leadList, true)
999
                    );
1000
                }
1001
1002
                unset($newLeadList);
1003
1004
                // Free some memory
1005
                gc_collect_cycles();
1006
1007
                if ($maxLeads && $leadsProcessed >= $maxLeads) {
1008
                    if ($output) {
1009
                        $progress->finish();
1010
                        $output->writeln('');
1011
                    }
1012
1013
                    return $leadsProcessed;
1014
                }
1015
            }
1016
1017
            if ($output) {
1018
                $progress->finish();
1019
                $output->writeln('');
1020
            }
1021
        }
1022
1023
        // Unset max ID to prevent capping at newly added max ID
1024
        unset($batchLimiters['maxId']);
1025
1026
        $orphanLeadsCount = $this->leadSegmentService->getOrphanedLeadListLeadsCount($leadList);
1027
1028
        // Ensure the same list is used each batch
1029
        $batchLimiters['maxId'] = (int) $orphanLeadsCount[$leadList->getId()]['maxId'];
1030
1031
        // Restart batching
1032
        $start     = $lastRoundPercentage     = 0;
1033
        $leadCount = $orphanLeadsCount[$leadList->getId()]['count'];
1034
1035
        if ($output) {
1036
            $output->writeln($this->translator->trans('mautic.lead.list.rebuild.to_be_removed', ['%leads%' => $leadCount, '%batch%' => $limit]));
1037
        }
1038
1039
        if ($leadCount) {
1040
            $maxCount = ($maxLeads) ? $maxLeads : $leadCount;
1041
1042
            if ($output) {
1043
                $progress = ProgressBarHelper::init($output, $maxCount);
1044
                $progress->start();
1045
            }
1046
1047
            // Remove leads
1048
            while ($start < $leadCount) {
1049
                // Keep CPU down for large lists; sleep per $limit batch
1050
                $this->batchSleep();
1051
1052
                $removeLeadList = $this->leadSegmentService->getOrphanedLeadListLeads($leadList, [], $limit);
1053
1054
                if (empty($removeLeadList[$leadList->getId()])) {
1055
                    // Somehow ran out of leads so break out
1056
                    break;
1057
                }
1058
1059
                $processedLeads = [];
1060
                foreach ($removeLeadList[$leadList->getId()] as $l) {
1061
                    $this->removeLead($l, $leadList, false, true, true);
1062
                    $processedLeads[] = $l;
1063
                    ++$leadsProcessed;
1064
                    if ($output && $leadsProcessed < $maxCount) {
1065
                        $progress->setProgress($leadsProcessed);
1066
                    }
1067
1068
                    if ($maxLeads && $leadsProcessed >= $maxLeads) {
1069
                        break;
1070
                    }
1071
                }
1072
1073
                // Dispatch batch event
1074
                if (count($processedLeads) && $this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_BATCH_CHANGE)) {
1075
                    $this->dispatcher->dispatch(
1076
                        LeadEvents::LEAD_LIST_BATCH_CHANGE,
1077
                        new ListChangeEvent($processedLeads, $leadList, false)
1078
                    );
1079
                }
1080
1081
                $start += $limit;
1082
1083
                unset($removeLeadList);
1084
1085
                // Free some memory
1086
                gc_collect_cycles();
1087
1088
                if ($maxLeads && $leadsProcessed >= $maxLeads) {
1089
                    if ($output) {
1090
                        $progress->finish();
1091
                        $output->writeln('');
1092
                    }
1093
1094
                    return $leadsProcessed;
1095
                }
1096
            }
1097
1098
            if ($output) {
1099
                $progress->finish();
1100
                $output->writeln('');
1101
            }
1102
        }
1103
1104
        return $leadsProcessed;
1105
    }
1106
1107
    /**
1108
     * Add lead to lists.
1109
     *
1110
     * @param array|Lead     $lead
1111
     * @param array|LeadList $lists
1112
     * @param bool           $manuallyAdded
1113
     * @param bool           $batchProcess
1114
     * @param int            $searchListLead  0 = reference, 1 = yes, -1 = known to not exist
1115
     * @param \DateTime      $dateManipulated
1116
     *
1117
     * @throws \Doctrine\ORM\ORMException
1118
     */
1119
    public function addLead($lead, $lists, $manuallyAdded = false, $batchProcess = false, $searchListLead = 1, $dateManipulated = null)
1120
    {
1121
        if (null == $dateManipulated) {
1122
            $dateManipulated = new \DateTime();
1123
        }
1124
1125
        if (!$lead instanceof Lead) {
1126
            $leadId = (is_array($lead) && isset($lead['id'])) ? $lead['id'] : $lead;
1127
            $lead   = $this->em->getReference('MauticLeadBundle:Lead', $leadId);
1128
        } else {
1129
            $leadId = $lead->getId();
1130
        }
1131
1132
        if (!$lists instanceof LeadList) {
1133
            //make sure they are ints
1134
            $searchForLists = [];
1135
            foreach ($lists as &$l) {
1136
                $l = (int) $l;
1137
                if (!isset($this->leadChangeLists[$l])) {
1138
                    $searchForLists[] = $l;
1139
                }
1140
            }
1141
1142
            if (!empty($searchForLists)) {
1143
                $listEntities = $this->getEntities([
1144
                    'filter' => [
1145
                        'force' => [
1146
                            [
1147
                                'column' => 'l.id',
1148
                                'expr'   => 'in',
1149
                                'value'  => $searchForLists,
1150
                            ],
1151
                        ],
1152
                    ],
1153
                ]);
1154
1155
                foreach ($listEntities as $list) {
1156
                    $this->leadChangeLists[$list->getId()] = $list;
1157
                }
1158
            }
1159
1160
            unset($listEntities, $searchForLists);
1161
        } else {
1162
            $this->leadChangeLists[$lists->getId()] = $lists;
1163
1164
            $lists = [$lists->getId()];
1165
        }
1166
1167
        if (!is_array($lists)) {
1168
            $lists = [$lists];
1169
        }
1170
1171
        $persistLists   = [];
1172
        $dispatchEvents = [];
1173
1174
        foreach ($lists as $listId) {
1175
            if (!isset($this->leadChangeLists[$listId])) {
1176
                // List no longer exists in the DB so continue to the next
1177
                continue;
1178
            }
1179
1180
            if (-1 == $searchListLead) {
1181
                $listLead = null;
1182
            } elseif ($searchListLead) {
1183
                $listLead = $this->getListLeadRepository()->findOneBy(
1184
                    [
1185
                        'lead' => $lead,
1186
                        'list' => $this->leadChangeLists[$listId],
1187
                    ]
1188
                );
1189
            } else {
1190
                $listLead = $this->em->getReference(ListLead::class,
1191
                    [
1192
                        'lead' => $leadId,
1193
                        'list' => $listId,
1194
                    ]
1195
                );
1196
            }
1197
1198
            if (null != $listLead) {
1199
                if ($manuallyAdded && $listLead->wasManuallyRemoved()) {
1200
                    $listLead->setManuallyRemoved(false);
1201
                    $listLead->setManuallyAdded($manuallyAdded);
1202
1203
                    $persistLists[]   = $listLead;
1204
                    $dispatchEvents[] = $listId;
1205
                } else {
1206
                    // Detach from Doctrine
1207
                    $this->em->detach($listLead);
1208
1209
                    continue;
1210
                }
1211
            } else {
1212
                $listLead = new ListLead();
1213
                $listLead->setList($this->leadChangeLists[$listId]);
1214
                $listLead->setLead($lead);
1215
                $listLead->setManuallyAdded($manuallyAdded);
1216
                $listLead->setDateAdded($dateManipulated);
1217
1218
                $persistLists[]   = $listLead;
1219
                $dispatchEvents[] = $listId;
1220
            }
1221
        }
1222
1223
        if (!empty($persistLists)) {
1224
            $this->getRepository()->saveEntities($persistLists);
1225
        }
1226
1227
        // Clear ListLead entities from Doctrine memory
1228
        $this->em->clear(ListLead::class);
1229
1230
        if ($batchProcess) {
1231
            // Detach for batch processing to preserve memory
1232
            $this->em->detach($lead);
1233
        } elseif (!empty($dispatchEvents) && ($this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_CHANGE))) {
1234
            foreach ($dispatchEvents as $listId) {
1235
                $event = new ListChangeEvent($lead, $this->leadChangeLists[$listId]);
1236
                $this->dispatcher->dispatch(LeadEvents::LEAD_LIST_CHANGE, $event);
1237
1238
                unset($event);
1239
            }
1240
        }
1241
1242
        unset($lead, $persistLists, $lists);
1243
    }
1244
1245
    /**
1246
     * Remove a lead from lists.
1247
     *
1248
     * @param      $lead
1249
     * @param      $lists
1250
     * @param bool $manuallyRemoved
1251
     * @param bool $batchProcess
1252
     * @param bool $skipFindOne
1253
     *
1254
     * @throws \Doctrine\ORM\ORMException
1255
     */
1256
    public function removeLead($lead, $lists, $manuallyRemoved = false, $batchProcess = false, $skipFindOne = false)
1257
    {
1258
        if (!$lead instanceof Lead) {
1259
            $leadId = (is_array($lead) && isset($lead['id'])) ? $lead['id'] : $lead;
1260
            $lead   = $this->em->getReference(Lead::class, $leadId);
1261
        } else {
1262
            $leadId = $lead->getId();
1263
        }
1264
1265
        if (!$lists instanceof LeadList) {
1266
            //make sure they are ints
1267
            $searchForLists = [];
1268
            foreach ($lists as &$l) {
1269
                $l = (int) $l;
1270
                if (!isset($this->leadChangeLists[$l])) {
1271
                    $searchForLists[] = $l;
1272
                }
1273
            }
1274
1275
            if (!empty($searchForLists)) {
1276
                $listEntities = $this->getEntities([
1277
                    'filter' => [
1278
                        'force' => [
1279
                            [
1280
                                'column' => 'l.id',
1281
                                'expr'   => 'in',
1282
                                'value'  => $searchForLists,
1283
                            ],
1284
                        ],
1285
                    ],
1286
                ]);
1287
1288
                foreach ($listEntities as $list) {
1289
                    $this->leadChangeLists[$list->getId()] = $list;
1290
                }
1291
            }
1292
1293
            unset($listEntities, $searchForLists);
1294
        } else {
1295
            $this->leadChangeLists[$lists->getId()] = $lists;
1296
1297
            $lists = [$lists->getId()];
1298
        }
1299
1300
        if (!is_array($lists)) {
1301
            $lists = [$lists];
1302
        }
1303
1304
        $persistLists   = [];
1305
        $deleteLists    = [];
1306
        $dispatchEvents = [];
1307
1308
        foreach ($lists as $listId) {
1309
            if (!isset($this->leadChangeLists[$listId])) {
1310
                // List no longer exists in the DB so continue to the next
1311
                continue;
1312
            }
1313
1314
            $listLead = (!$skipFindOne) ?
1315
                $this->getListLeadRepository()->findOneBy([
1316
                    'lead' => $lead,
1317
                    'list' => $this->leadChangeLists[$listId],
1318
                ]) :
1319
                $this->em->getReference(ListLead::class, [
1320
                    'lead' => $leadId,
1321
                    'list' => $listId,
1322
                ]);
1323
1324
            if (null == $listLead) {
1325
                // Lead is not part of this list
1326
                continue;
1327
            }
1328
1329
            if (($manuallyRemoved && $listLead->wasManuallyAdded()) || (!$manuallyRemoved && !$listLead->wasManuallyAdded())) {
1330
                //lead was manually added and now manually removed or was not manually added and now being removed
1331
                $deleteLists[]    = $listLead;
1332
                $dispatchEvents[] = $listId;
1333
            } elseif ($manuallyRemoved && !$listLead->wasManuallyAdded()) {
1334
                $listLead->setManuallyRemoved(true);
1335
1336
                $persistLists[]   = $listLead;
1337
                $dispatchEvents[] = $listId;
1338
            }
1339
1340
            unset($listLead);
1341
        }
1342
1343
        if (!empty($persistLists)) {
1344
            $this->getRepository()->saveEntities($persistLists);
1345
        }
1346
1347
        if (!empty($deleteLists)) {
1348
            $this->getRepository()->deleteEntities($deleteLists);
1349
        }
1350
1351
        // Clear ListLead entities from Doctrine memory
1352
        $this->em->clear(ListLead::class);
1353
1354
        if ($batchProcess) {
1355
            // Detach for batch processing to preserve memory
1356
            $this->em->detach($lead);
1357
        } elseif (!empty($dispatchEvents) && ($this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_CHANGE))) {
1358
            foreach ($dispatchEvents as $listId) {
1359
                $event = new ListChangeEvent($lead, $this->leadChangeLists[$listId], false);
1360
                $this->dispatcher->dispatch(LeadEvents::LEAD_LIST_CHANGE, $event);
1361
1362
                unset($event);
1363
            }
1364
        }
1365
1366
        unset($lead, $deleteLists, $persistLists, $lists);
1367
    }
1368
1369
    /**
1370
     * Batch sleep according to settings.
1371
     */
1372
    protected function batchSleep()
1373
    {
1374
        $leadSleepTime = $this->coreParametersHelper->get('batch_lead_sleep_time', false);
1375
        if (false === $leadSleepTime) {
1376
            $leadSleepTime = $this->coreParametersHelper->get('batch_sleep_time', 1);
1377
        }
1378
1379
        if (empty($leadSleepTime)) {
1380
            return;
1381
        }
1382
1383
        if ($leadSleepTime < 1) {
1384
            usleep($leadSleepTime * 1000000);
1385
        } else {
1386
            sleep($leadSleepTime);
1387
        }
1388
    }
1389
1390
    /**
1391
     * Get a list of top (by leads added) lists.
1392
     *
1393
     * @param int       $limit
1394
     * @param \DateTime $dateFrom
1395
     * @param \DateTime $dateTo
1396
     * @param bool      $canViewOthers
1397
     *
1398
     * @return array
1399
     */
1400
    public function getTopLists($limit = 10, $dateFrom = null, $dateTo = null, $canViewOthers = true)
1401
    {
1402
        $q = $this->em->getConnection()->createQueryBuilder();
1403
        $q->select('COUNT(t.date_added) AS leads, ll.id, ll.name, ll.alias')
1404
            ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
1405
            ->join('t', MAUTIC_TABLE_PREFIX.'lead_lists', 'll', 'll.id = t.leadlist_id')
1406
            ->orderBy('leads', 'DESC')
1407
            ->where($q->expr()->eq('ll.is_published', ':published'))
1408
            ->setParameter('published', true)
1409
            ->groupBy('ll.id')
1410
            ->setMaxResults($limit);
1411
1412
        if (!$canViewOthers) {
1413
            $q->andWhere('ll.created_by = :userId')
1414
                ->setParameter('userId', $this->userHelper->getUser()->getId());
1415
        }
1416
1417
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
1418
        $chartQuery->applyDateFilters($q, 'date_added');
1419
1420
        return $q->execute()->fetchAll();
1421
    }
1422
1423
    /**
1424
     * Get a list of top (by leads added) lists.
1425
     *
1426
     * @param int    $limit
1427
     * @param string $dateFrom
1428
     * @param string $dateTo
1429
     *
1430
     * @return array
1431
     */
1432
    public function getLifeCycleSegments($limit, $dateFrom, $dateTo, $canViewOthers, $segments)
1433
    {
1434
        if (!empty($segments)) {
1435
            $segmentlist = "'".implode("','", $segments)."'";
1436
        }
1437
        $q = $this->em->getConnection()->createQueryBuilder();
1438
        $q->select('COUNT(t.date_added) AS leads, ll.id, ll.name as name,ll.alias as alias')
1439
            ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
1440
            ->join('t', MAUTIC_TABLE_PREFIX.'lead_lists', 'll', 'll.id = t.leadlist_id')
1441
            ->join('t', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = t.lead_id')
1442
            ->orderBy('leads', 'DESC')
1443
            ->where($q->expr()->eq('ll.is_published', ':published'))
1444
            ->setParameter('published', true)
1445
            ->groupBy('ll.id');
1446
1447
        if ($limit) {
1448
            $q->setMaxResults($limit);
1449
        }
1450
        if (!empty($segments)) {
1451
            $q->andWhere('ll.id IN ('.$segmentlist.')');
1452
        }
1453
        if (!empty($dateFrom)) {
1454
            $q->andWhere("l.date_added >= '".$dateFrom->format('Y-m-d')."'");
1455
        }
1456
        if (!empty($dateTo)) {
1457
            $q->andWhere("l.date_added <= '".$dateTo->format('Y-m-d')." 23:59:59'");
1458
        }
1459
        if (!$canViewOthers) {
1460
            $q->andWhere('ll.created_by = :userId')
1461
                ->setParameter('userId', $this->userHelper->getUser()->getId());
1462
        }
1463
1464
        $results = $q->execute()->fetchAll();
1465
1466
        if (in_array(0, $segments)) {
1467
            $qAll = $this->em->getConnection()->createQueryBuilder();
1468
            $qAll->select('COUNT(t.date_added) AS leads, 0 as id, "All Contacts" as name, "" as alias')
1469
                ->from(MAUTIC_TABLE_PREFIX.'leads', 't');
1470
1471
            if (!$canViewOthers) {
1472
                $qAll->andWhere('ll.created_by = :userId')
1473
                    ->setParameter('userId', $this->userHelper->getUser()->getId());
1474
            }
1475
            if (!empty($dateFrom)) {
1476
                $qAll->andWhere("t.date_added >= '".$dateFrom->format('Y-m-d')."'");
1477
            }
1478
            if (!empty($dateTo)) {
1479
                $qAll->andWhere("t.date_added <= '".$dateTo->format('Y-m-d')." 23:59:59'");
1480
            }
1481
            $resultsAll = $qAll->execute()->fetchAll();
1482
            $results    = array_merge($results, $resultsAll);
1483
        }
1484
1485
        return $results;
1486
    }
1487
1488
    /**
1489
     * @param      $unit
1490
     * @param      $dateFormat
1491
     * @param      $filter
1492
     * @param bool $canViewOthers
1493
     * @param      $listName
1494
     *
1495
     * @return array
1496
     */
1497
    public function getLifeCycleSegmentChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat, $filter, $canViewOthers, $listName)
1498
    {
1499
        $chart = new PieChart();
1500
        $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
1501
1502
        if (!$canViewOthers) {
1503
            $filter['owner_id'] = $this->userHelper->getUser()->getId();
1504
        }
1505
1506
        if (isset($filter['flag'])) {
1507
            unset($filter['flag']);
1508
        }
1509
1510
        $allLists   = $query->getCountQuery('leads', 'id', 'date_added', null);
1511
        $lists      = $query->count('leads', 'id', 'date_added', $filter, null);
1512
        $all        = $query->fetchCount($allLists);
1513
        $identified = $lists;
1514
1515
        $chart->setDataset($listName, $identified);
1516
1517
        if (isset($filter['leadlist_id']['value'])) {
1518
            $chart->setDataset(
1519
                $this->translator->trans('mautic.lead.lifecycle.graph.pie.all.lists'),
1520
                $all
1521
            );
1522
        }
1523
1524
        return $chart->render(false);
1525
    }
1526
1527
    /**
1528
     * @param       $unit
1529
     * @param null  $dateFormat
1530
     * @param array $filter
1531
     * @param bool  $canViewOthers
1532
     *
1533
     * @return array
1534
     */
1535
    public function getStagesBarChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true)
1536
    {
1537
        $data['values'] = [];
1538
        $data['labels'] = [];
1539
1540
        $q = $this->em->getConnection()->createQueryBuilder();
1541
1542
        $q->select('count(l.id) as leads, s.name as stage')
1543
            ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
1544
            ->join('t', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = t.lead_id')
1545
            ->join('t', MAUTIC_TABLE_PREFIX.'stages', 's', 's.id=l.stage_id')
1546
            ->orderBy('leads', 'DESC')
1547
            ->where($q->expr()->eq('s.is_published', ':published'))
1548
1549
            ->andWhere($q->expr()->gte('t.date_added', ':date_from'))
1550
            ->setParameter('date_from', $dateFrom->format('Y-m-d'))
1551
            ->andWhere($q->expr()->lte('t.date_added', ':date_to'))
1552
            ->setParameter('date_to', $dateTo->format('Y-m-d'.' 23:59:59'))
1553
            ->setParameter('published', true);
1554
1555
        if (isset($filter['leadlist_id']['value'])) {
1556
            $q->andWhere($q->expr()->eq('t.leadlist_id', ':leadlistid'))->setParameter('leadlistid', $filter['leadlist_id']['value']);
1557
        }
1558
1559
        $q->groupBy('s.name');
1560
1561
        if (!$canViewOthers) {
1562
            $q->andWhere('s.created_by = :userId')
1563
                ->setParameter('userId', $this->userHelper->getUser()->getId());
1564
        }
1565
1566
        $results = $q->execute()->fetchAll();
1567
1568
        foreach ($results as $result) {
1569
            $data['labels'][] = substr($result['stage'], 0, 12);
1570
            $data['values'][] = $result['leads'];
1571
        }
1572
        $data['xAxes'][] = ['display' => true];
1573
        $data['yAxes'][] = ['display' => true];
1574
1575
        $baseData = [
1576
            'label' => $this->translator->trans('mautic.lead.leads'),
1577
            'data'  => $data['values'],
1578
        ];
1579
1580
        $chart      = new BarChart($data['labels']);
1581
        $datasets[] = array_merge($baseData, $chart->generateColors(3));
1582
1583
        return [
1584
            'labels'   => $data['labels'],
1585
            'datasets' => $datasets,
1586
            'options'  => [
1587
                'xAxes' => $data['xAxes'],
1588
                'yAxes' => $data['yAxes'],
1589
            ], ];
1590
    }
1591
1592
    /**
1593
     * @param       $unit
1594
     * @param null  $dateFormat
1595
     * @param array $filter
1596
     * @param bool  $canViewOthers
1597
     *
1598
     * @return array
1599
     */
1600
    public function getDeviceGranularityData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true)
1601
    {
1602
        $data['values'] = [];
1603
        $data['labels'] = [];
1604
1605
        $q = $this->em->getConnection()->createQueryBuilder();
1606
1607
        $q->select('count(l.id) as leads, ds.device')
1608
            ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
1609
            ->join('t', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = t.lead_id')
1610
            ->join('t', MAUTIC_TABLE_PREFIX.'page_hits', 'h', 'h.lead_id=l.id')
1611
            ->join('h', MAUTIC_TABLE_PREFIX.'lead_devices', 'ds', 'ds.id = h.device_id')
1612
            ->orderBy('ds.device', 'DESC')
1613
            ->andWhere($q->expr()->gte('t.date_added', ':date_from'))
1614
            ->setParameter('date_from', $dateFrom->format('Y-m-d'))
1615
            ->andWhere($q->expr()->lte('t.date_added', ':date_to'))
1616
            ->setParameter('date_to', $dateTo->format('Y-m-d'.' 23:59:59'));
1617
1618
        if (isset($filter['leadlist_id']['value'])) {
1619
            $q->andWhere($q->expr()->eq('t.leadlist_id', ':leadlistid'))->setParameter(
1620
                'leadlistid',
1621
                $filter['leadlist_id']['value']
1622
            );
1623
        }
1624
1625
        $q->groupBy('ds.device');
1626
1627
        if (!$canViewOthers) {
1628
            $q->andWhere('l.created_by = :userId')
1629
                ->setParameter('userId', $this->userHelper->getUser()->getId());
1630
        }
1631
1632
        $results = $q->execute()->fetchAll();
1633
1634
        foreach ($results as $result) {
1635
            $data['labels'][] = substr(empty($result['device']) ? $this->translator->trans('mautic.core.no.info') : $result['device'], 0, 12);
1636
            $data['values'][] = $result['leads'];
1637
        }
1638
1639
        $data['xAxes'][] = ['display' => true];
1640
        $data['yAxes'][] = ['display' => true];
1641
1642
        $baseData = [
1643
            'label' => $this->translator->trans('mautic.core.device'),
1644
            'data'  => $data['values'],
1645
        ];
1646
1647
        $chart      = new BarChart($data['labels']);
1648
        $datasets[] = array_merge($baseData, $chart->generateColors(2));
1649
1650
        return [
1651
            'labels'   => $data['labels'],
1652
            'datasets' => $datasets,
1653
            'options'  => [
1654
                'xAxes' => $data['xAxes'],
1655
                'yAxes' => $data['yAxes'],
1656
            ],
1657
        ];
1658
    }
1659
1660
    /**
1661
     * Get line chart data of hits.
1662
     *
1663
     * @param string $unit       {@link php.net/manual/en/function.date.php#refsect1-function.date-parameters}
1664
     * @param string $dateFormat
1665
     * @param array  $filter
1666
     *
1667
     * @return array
1668
     */
1669
    public function getSegmentContactsLineChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [])
1670
    {
1671
        $chart    = new LineChart($unit, $dateFrom, $dateTo, $dateFormat);
1672
        $query    = new SegmentContactsLineChartQuery($this->em->getConnection(), $dateFrom, $dateTo, $filter);
1673
1674
        // added line everytime
1675
        $chart->setDataset($this->translator->trans('mautic.lead.segments.contacts.added'), $this->segmentChartQueryFactory->getContactsAdded($query));
1676
1677
        // Just if we have event log data
1678
        // Added in 2.15 , then we can' display just from data from date range with event logs
1679
        if ($query->isStatsFromEventLog()) {
1680
            $chart->setDataset($this->translator->trans('mautic.lead.segments.contacts.removed'), $this->segmentChartQueryFactory->getContactsRemoved($query));
1681
            $chart->setDataset($this->translator->trans('mautic.lead.segments.contacts.total'), $this->segmentChartQueryFactory->getContactsTotal($query, $this));
1682
        }
1683
1684
        return $chart->render();
1685
    }
1686
1687
    /**
1688
     * Is custom field used in at least one defined segment?
1689
     *
1690
     * @return bool
1691
     */
1692
    public function isFieldUsed(LeadField $field)
1693
    {
1694
        return 0 < $this->getFieldSegments($field)->count();
1695
    }
1696
1697
    public function getFieldSegments(LeadField $field)
1698
    {
1699
        $alias       = $field->getAlias();
1700
        $aliasLength = mb_strlen($alias);
1701
        $likeContent = "%;s:5:\"field\";s:${aliasLength}:\"{$alias}\";%";
1702
        $filter      = [
1703
            'force'  => [
1704
                ['column' => 'l.filters', 'expr' => 'LIKE', 'value'=> $likeContent],
1705
            ],
1706
        ];
1707
1708
        return $this->getEntities(['filter' => $filter]);
1709
    }
1710
1711
    /**
1712
     * @param      $segmentId      *
1713
     * @param null $returnProperty property of entity in returned array, null return all entity
1714
     *
1715
     * @return array
1716
     */
1717
    public function getSegmentsWithDependenciesOnSegment($segmentId, $returnProperty = 'name')
1718
    {
1719
        $filter = [
1720
            'force'  => [
1721
                ['column' => 'l.filters', 'expr' => 'LIKE', 'value'=>'%s:8:"leadlist"%'],
1722
                ['column' => 'l.id', 'expr' => 'neq', 'value'=>$segmentId],
1723
            ],
1724
        ];
1725
        $entities = $this->getEntities(
1726
            [
1727
                'filter'     => $filter,
1728
            ]
1729
        );
1730
        $dependents = [];
1731
        $accessor   = new PropertyAccessor();
1732
        foreach ($entities as $entity) {
1733
            $retrFilters = $entity->getFilters();
1734
            foreach ($retrFilters as $eachFilter) {
1735
                if ('leadlist' === $eachFilter['type'] && in_array($segmentId, $eachFilter['filter'])) {
1736
                    if ($returnProperty && $value = $accessor->getValue($entity, $returnProperty)) {
1737
                        $dependents[] = $value;
1738
                    } else {
1739
                        $dependents[] = $entity;
1740
                    }
1741
                }
1742
            }
1743
        }
1744
1745
        return $dependents;
1746
    }
1747
1748
    /**
1749
     * Get segments which are used as a dependent by other segments to prevent batch deletion of them.
1750
     *
1751
     * @param array $segmentIds
1752
     *
1753
     * @return array
1754
     */
1755
    public function canNotBeDeleted($segmentIds)
1756
    {
1757
        $filter = [
1758
            'force'  => [
1759
                ['column' => 'l.filters', 'expr' => 'LIKE', 'value'=>'%s:8:"leadlist"%'],
1760
            ],
1761
        ];
1762
1763
        $entities = $this->getEntities(
1764
            [
1765
                'filter'     => $filter,
1766
            ]
1767
        );
1768
1769
        $idsNotToBeDeleted   = [];
1770
        $namesNotToBeDeleted = [];
1771
        $dependency          = [];
1772
1773
        foreach ($entities as $entity) {
1774
            $retrFilters = $entity->getFilters();
1775
            foreach ($retrFilters as $eachFilter) {
1776
                if ('leadlist' !== $eachFilter['type']) {
1777
                    continue;
1778
                }
1779
1780
                $idsNotToBeDeleted = array_unique(array_merge($idsNotToBeDeleted, $eachFilter['filter']));
1781
                foreach ($eachFilter['filter'] as $val) {
1782
                    if (!empty($dependency[$val])) {
1783
                        $dependency[$val] = array_merge($dependency[$val], [$entity->getId()]);
1784
                        $dependency[$val] = array_unique($dependency[$val]);
1785
                    } else {
1786
                        $dependency[$val] = [$entity->getId()];
1787
                    }
1788
                }
1789
            }
1790
        }
1791
        foreach ($dependency as $key => $value) {
1792
            if (array_intersect($value, $segmentIds) === $value) {
1793
                $idsNotToBeDeleted = array_unique(array_diff($idsNotToBeDeleted, [$key]));
1794
            }
1795
        }
1796
1797
        $idsNotToBeDeleted = array_intersect($segmentIds, $idsNotToBeDeleted);
1798
1799
        foreach ($idsNotToBeDeleted as $val) {
1800
            $namesNotToBeDeleted[$val] = $this->getEntity($val)->getName();
1801
        }
1802
1803
        return $namesNotToBeDeleted;
1804
    }
1805
}
1806