PageModel::getPageUrl()   F
last analyzed

Complexity

Conditions 23
Paths 1974

Size

Total Lines 87
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 45
nc 1974
nop 2
dl 0
loc 87
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\PageBundle\Model;
13
14
use Doctrine\DBAL\Query\QueryBuilder;
15
use Mautic\CoreBundle\Helper\Chart\ChartQuery;
16
use Mautic\CoreBundle\Helper\Chart\LineChart;
17
use Mautic\CoreBundle\Helper\Chart\PieChart;
18
use Mautic\CoreBundle\Helper\CookieHelper;
19
use Mautic\CoreBundle\Helper\CoreParametersHelper;
20
use Mautic\CoreBundle\Helper\DateTimeHelper;
21
use Mautic\CoreBundle\Helper\InputHelper;
22
use Mautic\CoreBundle\Helper\IpLookupHelper;
23
use Mautic\CoreBundle\Model\BuilderModelTrait;
24
use Mautic\CoreBundle\Model\FormModel;
25
use Mautic\CoreBundle\Model\TranslationModelTrait;
26
use Mautic\CoreBundle\Model\VariantModelTrait;
27
use Mautic\LeadBundle\DataObject\LeadManipulator;
28
use Mautic\LeadBundle\Entity\Company;
29
use Mautic\LeadBundle\Entity\Lead;
30
use Mautic\LeadBundle\Entity\UtmTag;
31
use Mautic\LeadBundle\Helper\IdentifyCompanyHelper;
32
use Mautic\LeadBundle\Model\CompanyModel;
33
use Mautic\LeadBundle\Model\FieldModel;
34
use Mautic\LeadBundle\Model\LeadModel;
35
use Mautic\LeadBundle\Tracker\ContactTracker;
36
use Mautic\LeadBundle\Tracker\DeviceTracker;
37
use Mautic\PageBundle\Entity\Hit;
38
use Mautic\PageBundle\Entity\Page;
39
use Mautic\PageBundle\Entity\Redirect;
40
use Mautic\PageBundle\Event\PageBuilderEvent;
41
use Mautic\PageBundle\Event\PageEvent;
42
use Mautic\PageBundle\Event\PageHitEvent;
43
use Mautic\PageBundle\Form\Type\PageType;
44
use Mautic\PageBundle\PageEvents;
45
use Mautic\QueueBundle\Queue\QueueName;
46
use Mautic\QueueBundle\Queue\QueueService;
47
use Symfony\Component\EventDispatcher\Event;
48
use Symfony\Component\HttpFoundation\Request;
49
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
50
51
class PageModel extends FormModel
52
{
53
    use TranslationModelTrait;
0 ignored issues
show
Bug introduced by
The trait Mautic\CoreBundle\Model\TranslationModelTrait requires the property $server which is not provided by Mautic\PageBundle\Model\PageModel.
Loading history...
54
    use VariantModelTrait;
55
    use BuilderModelTrait;
56
57
    /**
58
     * @var bool
59
     */
60
    protected $catInUrl;
61
62
    /**
63
     * @var CookieHelper
64
     */
65
    protected $cookieHelper;
66
67
    /**
68
     * @var IpLookupHelper
69
     */
70
    protected $ipLookupHelper;
71
72
    /**
73
     * @var LeadModel
74
     */
75
    protected $leadModel;
76
77
    /**
78
     * @var FieldModel
79
     */
80
    protected $leadFieldModel;
81
82
    /**
83
     * @var RedirectModel
84
     */
85
    protected $pageRedirectModel;
86
87
    /**
88
     * @var TrackableModel
89
     */
90
    protected $pageTrackableModel;
91
92
    /**
93
     * @var DateTimeHelper
94
     */
95
    protected $dateTimeHelper;
96
97
    /**
98
     * @var QueueService
99
     */
100
    protected $queueService;
101
102
    /**
103
     * @var CoreParametersHelper
104
     */
105
    protected $coreParametersHelper;
106
107
    /**
108
     * @var DeviceTracker
109
     */
110
    private $deviceTracker;
111
112
    /**
113
     * @var CompanyModel
114
     */
115
    private $companyModel;
116
117
    /**
118
     * @var ContactTracker
119
     */
120
    private $contactTracker;
121
122
    /**
123
     * PageModel constructor.
124
     */
125
    public function __construct(
126
        CookieHelper $cookieHelper,
127
        IpLookupHelper $ipLookupHelper,
128
        LeadModel $leadModel,
129
        FieldModel $leadFieldModel,
130
        RedirectModel $pageRedirectModel,
131
        TrackableModel $pageTrackableModel,
132
        QueueService $queueService,
133
        CompanyModel $companyModel,
134
        DeviceTracker $deviceTracker,
135
        ContactTracker $contactTracker,
136
        CoreParametersHelper $coreParametersHelper
137
    ) {
138
        $this->cookieHelper         = $cookieHelper;
139
        $this->ipLookupHelper       = $ipLookupHelper;
140
        $this->leadModel            = $leadModel;
141
        $this->leadFieldModel       = $leadFieldModel;
142
        $this->pageRedirectModel    = $pageRedirectModel;
143
        $this->pageTrackableModel   = $pageTrackableModel;
144
        $this->dateTimeHelper       = new DateTimeHelper();
145
        $this->queueService         = $queueService;
146
        $this->companyModel         = $companyModel;
147
        $this->deviceTracker        = $deviceTracker;
148
        $this->contactTracker       = $contactTracker;
149
        $this->coreParametersHelper = $coreParametersHelper;
150
    }
151
152
    /**
153
     * @param $catInUrl
154
     */
155
    public function setCatInUrl($catInUrl)
156
    {
157
        $this->catInUrl = $catInUrl;
158
    }
159
160
    /**
161
     * @return \Mautic\PageBundle\Entity\PageRepository
162
     */
163
    public function getRepository()
164
    {
165
        $repo = $this->em->getRepository('MauticPageBundle:Page');
166
        $repo->setCurrentUser($this->userHelper->getUser());
167
168
        return $repo;
169
    }
170
171
    /**
172
     * @return \Mautic\PageBundle\Entity\HitRepository
173
     */
174
    public function getHitRepository()
175
    {
176
        return $this->em->getRepository('MauticPageBundle:Hit');
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182
    public function getPermissionBase()
183
    {
184
        return 'page:pages';
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function getNameGetter()
191
    {
192
        return 'getTitle';
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     *
198
     * @param Page $entity
199
     * @param bool $unlock
200
     */
201
    public function saveEntity($entity, $unlock = true)
202
    {
203
        $pageIds = $entity->getRelatedEntityIds();
204
205
        if (empty($this->inConversion)) {
206
            $alias = $entity->getAlias();
207
            if (empty($alias)) {
208
                $alias = $entity->getTitle();
209
            }
210
            $alias = $this->cleanAlias($alias, '', false, '-');
211
212
            //make sure alias is not already taken
213
            $repo      = $this->getRepository();
214
            $testAlias = $alias;
215
            $count     = $repo->checkPageUniqueAlias($testAlias, $pageIds);
216
            $aliasTag  = 1;
217
218
            while ($count) {
219
                $testAlias = $alias.$aliasTag;
220
                $count     = $repo->checkPageUniqueAlias($testAlias, $pageIds);
221
                ++$aliasTag;
222
            }
223
            if ($testAlias != $alias) {
224
                $alias = $testAlias;
225
            }
226
            $entity->setAlias($alias);
227
        }
228
229
        // Set the author for new pages
230
        $isNew = $entity->isNew();
231
        if (!$isNew) {
232
            //increase the revision
233
            $revision = $entity->getRevision();
234
            ++$revision;
235
            $entity->setRevision($revision);
236
        }
237
238
        // Reset a/b test if applicable
239
        $variantStartDate = new \DateTime();
240
        $resetVariants    = $this->preVariantSaveEntity($entity, ['setVariantHits'], $variantStartDate);
241
242
        parent::saveEntity($entity, $unlock);
243
244
        $this->postVariantSaveEntity($entity, $resetVariants, $pageIds, $variantStartDate);
245
        $this->postTranslationEntitySave($entity);
246
    }
247
248
    /**
249
     * @param Page $entity
250
     */
251
    public function deleteEntity($entity)
252
    {
253
        if ($entity->isVariant() && $entity->getIsPublished()) {
254
            $this->resetVariants($entity);
255
        }
256
257
        parent::deleteEntity($entity);
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     *
263
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
264
     */
265
    public function createForm($entity, $formFactory, $action = null, $options = [])
266
    {
267
        if (!$entity instanceof Page) {
268
            throw new MethodNotAllowedHttpException(['Page']);
269
        }
270
271
        $formClass = PageType::class;
272
273
        if (!empty($options['formName'])) {
274
            $formClass = $options['formName'];
275
        }
276
277
        if (!empty($action)) {
278
            $options['action'] = $action;
279
        }
280
281
        return $formFactory->create($formClass, $entity, $options);
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     *
287
     * @return Page|null
288
     */
289
    public function getEntity($id = null)
290
    {
291
        if (null === $id) {
292
            $entity = new Page();
293
            $entity->setSessionId('new_'.hash('sha1', uniqid(mt_rand())));
294
        } else {
295
            $entity = parent::getEntity($id);
296
            if (null !== $entity) {
297
                $entity->setSessionId($entity->getId());
298
            }
299
        }
300
301
        return $entity;
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     *
307
     * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
308
     */
309
    protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null)
310
    {
311
        if (!$entity instanceof Page) {
312
            throw new MethodNotAllowedHttpException(['Page']);
313
        }
314
315
        switch ($action) {
316
            case 'pre_save':
317
                $name = PageEvents::PAGE_PRE_SAVE;
318
                break;
319
            case 'post_save':
320
                $name = PageEvents::PAGE_POST_SAVE;
321
                break;
322
            case 'pre_delete':
323
                $name = PageEvents::PAGE_PRE_DELETE;
324
                break;
325
            case 'post_delete':
326
                $name = PageEvents::PAGE_POST_DELETE;
327
                break;
328
            default:
329
                return null;
330
        }
331
332
        if ($this->dispatcher->hasListeners($name)) {
333
            if (empty($event)) {
334
                $event = new PageEvent($entity, $isNew);
335
                $event->setEntityManager($this->em);
336
            }
337
338
            $this->dispatcher->dispatch($name, $event);
339
340
            return $event;
341
        }
342
343
        return null;
344
    }
345
346
    /**
347
     * Get list of entities for autopopulate fields.
348
     *
349
     * @param string $type
350
     * @param string $filter
351
     * @param int    $limit
352
     *
353
     * @return array
354
     */
355
    public function getLookupResults($type, $filter = '', $limit = 10)
356
    {
357
        $results = [];
358
        switch ($type) {
359
            case 'page':
360
                $viewOther = $this->security->isGranted('page:pages:viewother');
361
                $repo      = $this->getRepository();
362
                $repo->setCurrentUser($this->userHelper->getUser());
363
                $results = $repo->getPageList($filter, $limit, 0, $viewOther);
364
                break;
365
        }
366
367
        return $results;
368
    }
369
370
    /**
371
     * Generate URL for a page.
372
     *
373
     * @param Page  $entity
374
     * @param bool  $absolute
375
     * @param array $clickthrough
376
     *
377
     * @return string
378
     */
379
    public function generateUrl($entity, $absolute = true, $clickthrough = [])
380
    {
381
        // If this is a variant, then get the parent's URL
382
        $parent = $entity->getVariantParent();
383
        if (null != $parent) {
384
            $entity = $parent;
385
        }
386
387
        $slug = $this->generateSlug($entity);
388
389
        return $this->buildUrl('mautic_page_public', ['slug' => $slug], $absolute, $clickthrough);
390
    }
391
392
    /**
393
     * Generates slug string.
394
     *
395
     * @param $entity
396
     *
397
     * @return string
398
     */
399
    public function generateSlug($entity)
400
    {
401
        $pageSlug = $entity->getAlias();
402
403
        //should the url include the category
404
        if ($this->catInUrl) {
405
            $category = $entity->getCategory();
406
            $catSlug  = (!empty($category))
407
                ? $category->getAlias()
408
                :
409
                $this->translator->trans('mautic.core.url.uncategorized');
410
        }
411
412
        $parent = $entity->getTranslationParent();
413
        $slugs  = [];
414
        if ($parent) {
415
            //multiple languages so tack on the language
416
            $slugs[] = $entity->getLanguage();
417
        }
418
419
        if (!empty($catSlug)) {
420
            // Insert category slug
421
            $slugs[] = $catSlug;
422
            $slugs[] = $pageSlug;
423
        } else {
424
            // Insert just the page slug
425
            $slugs[] = $pageSlug;
426
        }
427
428
        return implode('/', $slugs);
429
    }
430
431
    /**
432
     * @return array|mixed
433
     */
434
    protected function generateClickThrough(Hit $hit)
435
    {
436
        $query = $hit->getQuery();
437
438
        // Check for any clickthrough info
439
        $clickthrough = [];
440
        if (!empty($query['ct'])) {
441
            $clickthrough = $query['ct'];
442
            if (!is_array($clickthrough)) {
443
                $clickthrough = $this->decodeArrayFromUrl($clickthrough);
444
            }
445
        }
446
447
        return $clickthrough;
448
    }
449
450
    /**
451
     * @param Page|Redirect $page
452
     * @param string        $code
453
     * @param array         $query
454
     *
455
     * @throws \Exception
456
     */
457
    public function hitPage($page, Request $request, $code = '200', Lead $lead = null, $query = [])
458
    {
459
        // Don't skew results with user hits
460
        if (!$this->security->isAnonymous()) {
461
            return;
462
        }
463
464
        // Process the query
465
        if (empty($query)) {
466
            $query = $this->getHitQuery($request, $page);
467
        }
468
469
        // Get lead if required
470
        if (null == $lead) {
471
            $lead = $this->leadModel->getContactFromRequest($query);
472
473
            // company
474
            list($company, $leadAdded, $companyEntity) = IdentifyCompanyHelper::identifyLeadsCompany($query, $lead, $this->companyModel);
475
            if ($leadAdded) {
476
                $lead->addCompanyChangeLogEntry('form', 'Identify Company', 'Lead added to the company, '.$company['companyname'], $company['id']);
477
            } elseif ($companyEntity instanceof Company) {
478
                $this->companyModel->setFieldValues($companyEntity, $query);
479
                $this->companyModel->saveEntity($companyEntity);
480
            }
481
482
            if (!empty($company) and $companyEntity instanceof Company) {
483
                // Save after the lead in for new leads created through the API and maybe other places
484
                $this->companyModel->addLeadToCompany($companyEntity, $lead);
485
                $this->leadModel->setPrimaryCompany($companyEntity->getId(), $lead->getId());
486
            }
487
        }
488
489
        if (!$lead || !$lead->getId()) {
0 ignored issues
show
introduced by
$lead is of type Mautic\LeadBundle\Entity\Lead, thus it always evaluated to true.
Loading history...
490
            // Lead came from a non-trackable IP so ignore
491
            return;
492
        }
493
494
        $hit = new Hit();
495
        $hit->setDateHit(new \Datetime());
496
        $hit->setIpAddress($this->ipLookupHelper->getIpAddress());
497
498
        // Set info from request
499
        $hit->setQuery($query);
500
        $hit->setCode($code);
501
502
        $trackedDevice = $this->deviceTracker->createDeviceFromUserAgent($lead, $request->server->get('HTTP_USER_AGENT'));
503
504
        $hit->setTrackingId($trackedDevice->getTrackingId());
505
        $hit->setDeviceStat($trackedDevice);
0 ignored issues
show
Bug introduced by
It seems like $trackedDevice can also be of type null; however, parameter $device of Mautic\PageBundle\Entity\Hit::setDeviceStat() does only seem to accept Mautic\LeadBundle\Entity\LeadDevice, 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

505
        $hit->setDeviceStat(/** @scrutinizer ignore-type */ $trackedDevice);
Loading history...
506
507
        // Wrap in a try/catch to prevent deadlock errors on busy servers
508
        try {
509
            $this->em->persist($hit);
510
            $this->em->flush();
511
        } catch (\Exception $exception) {
512
            if (MAUTIC_ENV === 'dev') {
513
                throw $exception;
514
            } else {
515
                $this->logger->addError(
516
                    $exception->getMessage(),
517
                    ['exception' => $exception]
518
                );
519
            }
520
        }
521
522
        //save hit to the cookie to use to update the exit time
523
        if ($hit) {
0 ignored issues
show
introduced by
$hit is of type Mautic\PageBundle\Entity\Hit, thus it always evaluated to true.
Loading history...
524
            $this->cookieHelper->setCookie('mautic_referer_id', $hit->getId() ?: null);
525
        }
526
527
        if ($this->queueService->isQueueEnabled()) {
528
            $msg = [
529
                'hitId'         => $hit->getId(),
530
                'pageId'        => $page ? $page->getId() : null,
531
                'request'       => $request,
532
                'leadId'        => $lead ? $lead->getId() : null,
0 ignored issues
show
introduced by
$lead is of type Mautic\LeadBundle\Entity\Lead, thus it always evaluated to true.
Loading history...
533
                'isNew'         => $this->deviceTracker->wasDeviceChanged(),
534
                'isRedirect'    => ($page instanceof Redirect),
535
            ];
536
            $this->queueService->publishToQueue(QueueName::PAGE_HIT, $msg);
537
        } else {
538
            $this->processPageHit($hit, $page, $request, $lead, $this->deviceTracker->wasDeviceChanged());
539
        }
540
    }
541
542
    /**
543
     * Process page hit.
544
     *
545
     * @param Page|Redirect $page
546
     * @param bool          $trackingNewlyGenerated
547
     * @param bool          $activeRequest
548
     *
549
     * @throws \Exception
550
     */
551
    public function processPageHit(Hit $hit, $page, Request $request, Lead $lead, $trackingNewlyGenerated, $activeRequest = true)
552
    {
553
        // Store Page/Redirect association
554
        if ($page) {
555
            if ($page instanceof Page) {
556
                $hit->setPage($page);
557
            } else {
558
                $hit->setRedirect($page);
559
            }
560
        }
561
562
        // Check for any clickthrough info
563
        $clickthrough = $this->generateClickThrough($hit);
564
        if (!empty($clickthrough)) {
565
            if (!empty($clickthrough['channel'])) {
566
                if (1 === count($clickthrough['channel'])) {
567
                    $channelId = reset($clickthrough['channel']);
568
                    $channel   = key($clickthrough['channel']);
569
                } else {
570
                    $channel   = $clickthrough['channel'][0];
571
                    $channelId = (int) $clickthrough['channel'][1];
572
                }
573
                $hit->setSource($channel);
574
                $hit->setSourceId($channelId);
575
            } elseif (!empty($clickthrough['source'])) {
576
                $hit->setSource($clickthrough['source'][0]);
577
                $hit->setSourceId($clickthrough['source'][1]);
578
            }
579
580
            if (!empty($clickthrough['email'])) {
581
                $emailRepo = $this->em->getRepository('MauticEmailBundle:Email');
582
                if ($emailEntity = $emailRepo->getEntity($clickthrough['email'])) {
583
                    $hit->setEmail($emailEntity);
584
                }
585
            }
586
        }
587
588
        $query = $hit->getQuery() ? $hit->getQuery() : [];
589
590
        if (isset($query['timezone_offset']) && !$lead->getTimezone()) {
591
            // timezone_offset holds timezone offset in minutes. Multiply by 60 to get seconds.
592
            // Multiply by -1 because Firgerprint2 seems to have it the other way around.
593
            $timezone = (-1 * $query['timezone_offset'] * 60);
594
            $lead->setTimezone($this->dateTimeHelper->guessTimezoneFromOffset($timezone));
595
        }
596
597
        $query = $this->cleanQuery($query);
598
599
        if (isset($query['page_referrer'])) {
600
            $hit->setReferer($query['page_referrer']);
601
        }
602
        if (isset($query['page_language'])) {
603
            $hit->setPageLanguage($query['page_language']);
604
        }
605
        if (isset($query['page_title'])) {
606
            // Transliterate page titles.
607
            if ($this->coreParametersHelper->get('transliterate_page_title')) {
608
                $safeTitle = InputHelper::transliterate($query['page_title']);
609
                $hit->setUrlTitle($safeTitle);
610
                $query['page_title'] = $safeTitle;
611
            } else {
612
                $hit->setUrlTitle($query['page_title']);
613
            }
614
        }
615
616
        $hit->setQuery($query);
617
        $hit->setUrl((isset($query['page_url'])) ? $query['page_url'] : $request->getRequestUri());
618
619
        // Add entry to contact log table
620
        $this->setLeadManipulator($page, $hit, $lead);
621
622
        // Store tracking ID
623
        $hit->setLead($lead);
624
625
        if (!$activeRequest) {
626
            // Queue is consuming this hit outside of the lead's active request so this must be set in order for listeners to know who the request belongs to
627
            $this->contactTracker->setSystemContact($lead);
628
        }
629
        $trackingId = $hit->getTrackingId();
630
        if (!$trackingNewlyGenerated) {
631
            $lastHit = $request->cookies->get('mautic_referer_id');
632
            if (!empty($lastHit)) {
633
                //this is not a new session so update the last hit if applicable with the date/time the user left
634
                $this->getHitRepository()->updateHitDateLeft($lastHit);
635
            }
636
        }
637
638
        // Check if this is a unique page hit
639
        $isUnique = $this->getHitRepository()->isUniquePageHit($page, $trackingId, $lead);
640
641
        if (!empty($page)) {
642
            if ($page instanceof Page) {
643
                $hit->setPageLanguage($page->getLanguage());
644
645
                $isVariant = ($isUnique) ? $page->getVariantStartDate() : false;
646
647
                try {
648
                    $this->getRepository()->upHitCount($page->getId(), 1, $isUnique, !empty($isVariant));
649
                } catch (\Exception $exception) {
650
                    $this->logger->addError(
651
                        $exception->getMessage(),
652
                        ['exception' => $exception]
653
                    );
654
                }
655
            } elseif ($page instanceof Redirect) {
0 ignored issues
show
introduced by
$page is always a sub-type of Mautic\PageBundle\Entity\Redirect.
Loading history...
656
                try {
657
                    $this->pageRedirectModel->getRepository()->upHitCount($page->getId(), 1, $isUnique);
658
659
                    // If this is a trackable, up the trackable counts as well
660
                    if ($hit->getSource() && $hit->getSourceId()) {
661
                        $this->pageTrackableModel->getRepository()->upHitCount(
662
                            $page->getId(),
663
                            $hit->getSource(),
664
                            $hit->getSourceId(),
665
                            1,
666
                            $isUnique
667
                        );
668
                    }
669
                } catch (\Exception $exception) {
670
                    if (MAUTIC_ENV === 'dev') {
671
                        throw $exception;
672
                    } else {
673
                        $this->logger->addError(
674
                            $exception->getMessage(),
675
                            ['exception' => $exception]
676
                        );
677
                    }
678
                }
679
            }
680
        }
681
682
        //glean info from the IP address
683
        $ipAddress = $hit->getIpAddress();
684
        if ($details = $ipAddress->getIpDetails()) {
685
            $hit->setCountry($details['country']);
686
            $hit->setRegion($details['region']);
687
            $hit->setCity($details['city']);
688
            $hit->setIsp($details['isp']);
689
            $hit->setOrganization($details['organization']);
690
        }
691
692
        if (!$hit->getReferer()) {
693
            $hit->setReferer($request->server->get('HTTP_REFERER'));
694
        }
695
696
        $hit->setUserAgent($request->server->get('HTTP_USER_AGENT'));
697
        $hit->setRemoteHost($request->server->get('REMOTE_HOST'));
698
699
        if ($isUnique) {
700
            // Add UTM tags entry if a UTM tag exist
701
            $queryHasUtmTags = false;
702
            if (!is_array($query)) {
703
                parse_str($query, $query);
704
            }
705
706
            foreach ($query as $key => $value) {
707
                if (false !== strpos($key, 'utm_')) {
708
                    $queryHasUtmTags = true;
709
                    break;
710
                }
711
            }
712
713
            if ($queryHasUtmTags && $lead) {
714
                $utmTags = new UtmTag();
715
                $utmTags->setDateAdded($hit->getDateHit());
716
                $utmTags->setUrl($hit->getUrl());
717
                $utmTags->setReferer($hit->getReferer());
718
                $utmTags->setQuery($hit->getQuery());
719
                $utmTags->setUserAgent($hit->getUserAgent());
720
                $utmTags->setRemoteHost($hit->getRemoteHost());
721
                $utmTags->setLead($lead);
722
723
                if (array_key_exists('utm_campaign', $query)) {
724
                    $utmTags->setUtmCampaign($query['utm_campaign']);
725
                }
726
                if (array_key_exists('utm_term', $query)) {
727
                    $utmTags->setUtmTerm($query['utm_term']);
728
                }
729
                if (array_key_exists('utm_content', $query)) {
730
                    $utmTags->setUtmContent($query['utm_content']);
731
                }
732
                if (array_key_exists('utm_medium', $query)) {
733
                    $utmTags->setUtmMedium($query['utm_medium']);
734
                }
735
                if (array_key_exists('utm_source', $query)) {
736
                    $utmTags->setUtmSource($query['utm_source']);
737
                }
738
739
                $repo = $this->em->getRepository('MauticLeadBundle:UtmTag');
740
                $repo->saveEntity($utmTags);
741
742
                $this->leadModel->setUtmTags($lead, $utmTags);
743
            }
744
        }
745
        //get a list of the languages the user prefers
746
        $browserLanguages = $request->server->get('HTTP_ACCEPT_LANGUAGE');
747
        if (!empty($browserLanguages)) {
748
            $languages = explode(',', $browserLanguages);
749
            foreach ($languages as $k => $l) {
750
                if ($pos = false !== strpos(';q=', $l)) {
751
                    //remove weights
752
                    $languages[$k] = substr($l, 0, $pos);
753
                }
754
            }
755
            $hit->setBrowserLanguages($languages);
756
        }
757
758
        // Wrap in a try/catch to prevent deadlock errors on busy servers
759
        try {
760
            $this->em->persist($hit);
761
            $this->em->flush();
762
        } catch (\Exception $exception) {
763
            if (MAUTIC_ENV === 'dev') {
764
                throw $exception;
765
            } else {
766
                $this->logger->addError(
767
                    $exception->getMessage(),
768
                    ['exception' => $exception]
769
                );
770
            }
771
        }
772
773
        if ($this->dispatcher->hasListeners(PageEvents::PAGE_ON_HIT)) {
774
            $event = new PageHitEvent($hit, $request, $hit->getCode(), $clickthrough, $isUnique);
775
            $this->dispatcher->dispatch(PageEvents::PAGE_ON_HIT, $event);
776
        }
777
    }
778
779
    /**
780
     * @param Redirect|Page|null $page
781
     *
782
     * @return array
783
     */
784
    public function getHitQuery(Request $request, $page = null)
785
    {
786
        $get  = $request->query->all();
787
        $post = $request->request->all();
788
789
        $query = \array_merge($get, $post);
790
791
        // Set generated page url
792
        $query['page_url'] = $this->getPageUrl($request, $page);
793
794
        // Process clickthrough if applicable
795
        if (!empty($query['ct'])) {
796
            $query['ct'] = $this->decodeArrayFromUrl($query['ct']);
797
        }
798
799
        return $query;
800
    }
801
802
    /**
803
     * Get array of page builder tokens from bundles subscribed PageEvents::PAGE_ON_BUILD.
804
     *
805
     * @param array|string $requestedComponents all | tokens | abTestWinnerCriteria
806
     * @param string|null  $tokenFilter
807
     *
808
     * @return array
809
     */
810
    public function getBuilderComponents(Page $page = null, $requestedComponents = 'all', $tokenFilter = null)
811
    {
812
        $event = new PageBuilderEvent($this->translator, $page, $requestedComponents, $tokenFilter);
813
        $this->dispatcher->dispatch(PageEvents::PAGE_ON_BUILD, $event);
814
815
        return $this->getCommonBuilderComponents($requestedComponents, $event);
816
    }
817
818
    /**
819
     * Get number of page bounces.
820
     *
821
     * @param \DateTime $fromDate
822
     *
823
     * @return int
824
     */
825
    public function getBounces(Page $page, \DateTime $fromDate = null)
826
    {
827
        return $this->getHitRepository()->getBounces($page->getId(), $fromDate);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getHitRepo...ge->getId(), $fromDate) returns the type array which is incompatible with the documented return type integer.
Loading history...
828
    }
829
830
    /**
831
     * Joins the page table and limits created_by to currently logged in user.
832
     */
833
    public function limitQueryToCreator(QueryBuilder &$q)
834
    {
835
        $q->join('t', MAUTIC_TABLE_PREFIX.'pages', 'p', 'p.id = t.page_id')
836
            ->andWhere('p.created_by = :userId')
837
            ->setParameter('userId', $this->userHelper->getUser()->getId());
838
    }
839
840
    /**
841
     * Get line chart data of hits.
842
     *
843
     * @param char   $unit          {@link php.net/manual/en/function.date.php#refsect1-function.date-parameters}
0 ignored issues
show
Bug introduced by
The type Mautic\PageBundle\Model\char was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
844
     * @param string $dateFormat
845
     * @param array  $filter
846
     * @param bool   $canViewOthers
847
     *
848
     * @return array
849
     */
850
    public function getHitsLineChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true)
851
    {
852
        $flag = null;
853
854
        if (isset($filter['flag'])) {
855
            $flag = $filter['flag'];
856
            unset($filter['flag']);
857
        }
858
859
        $chart = new LineChart($unit, $dateFrom, $dateTo, $dateFormat);
860
        $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
861
862
        if (!$flag || 'total_and_unique' == $flag) {
863
            $q = $query->prepareTimeDataQuery('page_hits', 'date_hit', $filter);
864
865
            if (!$canViewOthers) {
866
                $this->limitQueryToCreator($q);
867
            }
868
869
            $data = $query->loadAndBuildTimeData($q);
870
            $chart->setDataset($this->translator->trans('mautic.page.show.total.visits'), $data);
871
        }
872
873
        if ('unique' == $flag || 'total_and_unique' == $flag) {
874
            $q = $query->prepareTimeDataQuery('page_hits', 'date_hit', $filter, 'distinct(t.lead_id)');
875
876
            if (!$canViewOthers) {
877
                $this->limitQueryToCreator($q);
878
            }
879
880
            $data = $query->loadAndBuildTimeData($q);
881
            $chart->setDataset($this->translator->trans('mautic.page.show.unique.visits'), $data);
882
        }
883
884
        return $chart->render();
885
    }
886
887
    /**
888
     * Get data for pie chart showing new vs returning leads.
889
     * Returning leads are even leads who visits 2 different page once.
890
     *
891
     * @param \DateTime $dateFrom
892
     * @param \DateTime $dateTo
893
     * @param array     $filters
894
     * @param bool      $canViewOthers
895
     *
896
     * @return array
897
     */
898
    public function getNewVsReturningPieChartData($dateFrom, $dateTo, $filters = [], $canViewOthers = true)
899
    {
900
        $chart              = new PieChart();
901
        $query              = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
902
        $allQ               = $query->getCountQuery('page_hits', 'id', 'date_hit', $filters);
903
        $filters['lead_id'] = [
904
            'expression' => 'isNull',
905
        ];
906
        $returnQ            = $query->getCountQuery('page_hits', 'id', 'date_hit', $filters);
907
908
        if (!$canViewOthers) {
909
            $this->limitQueryToCreator($allQ);
910
            $this->limitQueryToCreator($returnQ);
911
        }
912
913
        $all       = $query->fetchCount($allQ);
914
        $returning = $query->fetchCount($returnQ);
915
        $unique    = $all - $returning;
916
        $chart->setDataset($this->translator->trans('mautic.page.unique'), $unique);
917
        $chart->setDataset($this->translator->trans('mautic.page.graph.pie.new.vs.returning.returning'), $returning);
918
919
        return $chart->render();
920
    }
921
922
    /**
923
     * Get pie chart data of dwell times.
924
     *
925
     * @param array $filters
926
     * @param bool  $canViewOthers
927
     *
928
     * @return array
929
     */
930
    public function getDwellTimesPieChartData(\DateTime $dateFrom, \DateTime $dateTo, $filters = [], $canViewOthers = true)
931
    {
932
        $timesOnSite = $this->getHitRepository()->getDwellTimeLabels();
933
        $chart       = new PieChart();
934
        $query       = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
935
936
        foreach ($timesOnSite as $time) {
937
            $q = $query->getCountDateDiffQuery('page_hits', 'date_hit', 'date_left', $time['from'], $time['till'], $filters);
938
939
            if (!$canViewOthers) {
940
                $this->limitQueryToCreator($q);
941
            }
942
943
            $data = $query->fetchCountDateDiff($q);
944
            $chart->setDataset($time['label'], $data);
945
        }
946
947
        return $chart->render();
948
    }
949
950
    /**
951
     * Get bar chart data of hits.
952
     *
953
     * @param DateTime $dateFrom
0 ignored issues
show
Bug introduced by
The type Mautic\PageBundle\Model\DateTime was not found. Did you mean DateTime? If so, make sure to prefix the type with \.
Loading history...
954
     * @param DateTime $dateTo
955
     *
956
     * @return array
957
     */
958
    public function getDeviceGranularityData(\DateTime $dateFrom, \DateTime $dateTo, $filters = [], $canViewOthers = true)
0 ignored issues
show
Unused Code introduced by
The parameter $filters is not used and could be removed. ( Ignorable by Annotation )

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

958
    public function getDeviceGranularityData(\DateTime $dateFrom, \DateTime $dateTo, /** @scrutinizer ignore-unused */ $filters = [], $canViewOthers = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $canViewOthers is not used and could be removed. ( Ignorable by Annotation )

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

958
    public function getDeviceGranularityData(\DateTime $dateFrom, \DateTime $dateTo, $filters = [], /** @scrutinizer ignore-unused */ $canViewOthers = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
959
    {
960
        $q = $this->em->getConnection()->createQueryBuilder();
961
962
        $q->select('count(h.id) as count, ds.device as device')
963
            ->from(MAUTIC_TABLE_PREFIX.'page_hits', 'h')
964
            ->join('h', MAUTIC_TABLE_PREFIX.'lead_devices', 'ds', 'ds.id=h.device_id')
965
            ->orderBy('device', 'DESC')
966
            ->andWhere($q->expr()->gte('h.date_hit', ':date_from'))
967
            ->setParameter('date_from', $dateFrom->format('Y-m-d'))
968
            ->andWhere($q->expr()->lte('h.date_hit', ':date_to'))
969
            ->setParameter('date_to', $dateTo->format('Y-m-d'.' 23:59:59'));
970
        $q->groupBy('ds.device');
971
972
        $results = $q->execute()->fetchAll();
973
        $chart   = new PieChart();
974
975
        if (empty($results)) {
976
            $results[] = [
977
                'device' => $this->translator->trans('mautic.report.report.noresults'),
978
                'count'  => 0,
979
            ];
980
        }
981
982
        foreach ($results as $result) {
983
            $label = empty($result['device']) ? $this->translator->trans('mautic.core.no.info') : $result['device'];
984
985
            $chart->setDataset($label, $result['count']);
986
        }
987
988
        return $chart->render();
989
    }
990
991
    /**
992
     * Get a list of popular (by hits) pages.
993
     *
994
     * @param int       $limit
995
     * @param \DateTime $dateFrom
996
     * @param \DateTime $dateTo
997
     * @param array     $filters
998
     * @param bool      $canViewOthers
999
     *
1000
     * @return array
1001
     */
1002
    public function getPopularPages($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $canViewOthers = true)
1003
    {
1004
        $q = $this->em->getConnection()->createQueryBuilder();
1005
        $q->select('COUNT(DISTINCT t.id) AS hits, p.id, p.title, p.alias')
1006
            ->from(MAUTIC_TABLE_PREFIX.'page_hits', 't')
1007
            ->join('t', MAUTIC_TABLE_PREFIX.'pages', 'p', 'p.id = t.page_id')
1008
            ->orderBy('hits', 'DESC')
1009
            ->groupBy('p.id')
1010
            ->setMaxResults($limit);
1011
1012
        if (!$canViewOthers) {
1013
            $q->andWhere('p.created_by = :userId')
1014
                ->setParameter('userId', $this->userHelper->getUser()->getId());
1015
        }
1016
1017
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
0 ignored issues
show
Bug introduced by
It seems like $dateFrom can also be of type null; however, parameter $dateFrom of Mautic\CoreBundle\Helper...artQuery::__construct() does only seem to accept DateTime, 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

1017
        $chartQuery = new ChartQuery($this->em->getConnection(), /** @scrutinizer ignore-type */ $dateFrom, $dateTo);
Loading history...
Bug introduced by
It seems like $dateTo can also be of type null; however, parameter $dateTo of Mautic\CoreBundle\Helper...artQuery::__construct() does only seem to accept DateTime, 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

1017
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, /** @scrutinizer ignore-type */ $dateTo);
Loading history...
1018
        $chartQuery->applyFilters($q, $filters);
1019
        $chartQuery->applyDateFilters($q, 'date_hit');
1020
1021
        return $q->execute()->fetchAll();
1022
    }
1023
1024
    /**
1025
     * Get a list of pages created in a date range.
1026
     *
1027
     * @param int       $limit
1028
     * @param \DateTime $dateFrom
1029
     * @param \DateTime $dateTo
1030
     * @param array     $filters
1031
     * @param bool      $canViewOthers
1032
     *
1033
     * @return array
1034
     */
1035
    public function getPageList($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $canViewOthers = true)
1036
    {
1037
        $q = $this->em->getConnection()->createQueryBuilder();
1038
        $q->select('t.id, t.title AS name, t.date_added, t.date_modified')
1039
            ->from(MAUTIC_TABLE_PREFIX.'pages', 't')
1040
            ->setMaxResults($limit);
1041
1042
        if (!$canViewOthers) {
1043
            $q->andWhere('t.created_by = :userId')
1044
                ->setParameter('userId', $this->userHelper->getUser()->getId());
1045
        }
1046
1047
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
0 ignored issues
show
Bug introduced by
It seems like $dateFrom can also be of type null; however, parameter $dateFrom of Mautic\CoreBundle\Helper...artQuery::__construct() does only seem to accept DateTime, 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

1047
        $chartQuery = new ChartQuery($this->em->getConnection(), /** @scrutinizer ignore-type */ $dateFrom, $dateTo);
Loading history...
Bug introduced by
It seems like $dateTo can also be of type null; however, parameter $dateTo of Mautic\CoreBundle\Helper...artQuery::__construct() does only seem to accept DateTime, 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

1047
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, /** @scrutinizer ignore-type */ $dateTo);
Loading history...
1048
        $chartQuery->applyFilters($q, $filters);
1049
        $chartQuery->applyDateFilters($q, 'date_added');
1050
1051
        return $q->execute()->fetchAll();
1052
    }
1053
1054
    /**
1055
     * @param $page
1056
     */
1057
    private function setLeadManipulator($page, Hit $hit, Lead $lead)
1058
    {
1059
        // Only save the lead and dispatch events if needed
1060
        $source   = 'hit';
1061
        $sourceId = $hit->getId();
1062
        if ($page) {
1063
            $source   = $page instanceof Page ? 'page' : 'redirect';
1064
            $sourceId = $page->getId();
1065
        }
1066
1067
        $lead->setManipulator(
1068
            new LeadManipulator(
1069
                'page',
1070
                $source,
1071
                $sourceId,
1072
                $hit->getUrl()
1073
            )
1074
        );
1075
1076
        $this->leadModel->saveEntity($lead);
1077
    }
1078
1079
    /**
1080
     * @param $page
1081
     *
1082
     * @return mixed|string
1083
     */
1084
    private function getPageUrl(Request $request, $page)
1085
    {
1086
        // Default to page_url set in the query from tracking pixel and/or contactfield token
1087
        if ($pageURL = $request->get('page_url')) {
1088
            return $pageURL;
1089
        }
1090
1091
        if ($page instanceof Redirect) {
1092
            //use the configured redirect URL
1093
            return $page->getUrl();
1094
        }
1095
1096
        // Use the current URL
1097
        $isPageEvent = false;
1098
        if (false !== strpos($request->server->get('REQUEST_URI'), $this->router->generate('mautic_page_tracker'))) {
1099
            // Tracking pixel is used
1100
            if ($request->server->get('QUERY_STRING')) {
1101
                parse_str($request->server->get('QUERY_STRING'), $query);
1102
                $isPageEvent = true;
1103
            }
1104
        } elseif (false !== strpos($request->server->get('REQUEST_URI'), $this->router->generate('mautic_page_tracker_cors'))) {
1105
            $query       = $request->request->all();
1106
            $isPageEvent = true;
1107
        }
1108
1109
        if ($isPageEvent) {
1110
            $pageURL = $request->server->get('HTTP_REFERER');
1111
1112
            // if additional data were sent with the tracking pixel
1113
            if (isset($query)) {
1114
                // URL attr 'd' is encoded so let's decode it first.
1115
                $decoded = false;
1116
                if (isset($query['d'])) {
1117
                    // parse_str auto urldecodes
1118
                    $query   = $this->decodeArrayFromUrl($query['d'], false);
1119
                    $decoded = true;
1120
                }
1121
1122
                if (is_array($query) && !empty($query)) {
1123
                    if (isset($query['page_url'])) {
1124
                        $pageURL = $query['page_url'];
1125
                        if (!$decoded) {
1126
                            $pageURL = urldecode($pageURL);
1127
                        }
1128
                    }
1129
1130
                    if (isset($query['page_referrer'])) {
1131
                        if (!$decoded) {
1132
                            $query['page_referrer'] = urldecode($query['page_referrer']);
1133
                        }
1134
                    }
1135
1136
                    if (isset($query['page_language'])) {
1137
                        if (!$decoded) {
1138
                            $query['page_language'] = urldecode($query['page_language']);
1139
                        }
1140
                    }
1141
1142
                    if (isset($query['page_title'])) {
1143
                        if (!$decoded) {
1144
                            $query['page_title'] = urldecode($query['page_title']);
1145
                        }
1146
                    }
1147
1148
                    if (isset($query['tags'])) {
1149
                        if (!$decoded) {
1150
                            $query['tags'] = urldecode($query['tags']);
1151
                        }
1152
                    }
1153
                }
1154
            }
1155
1156
            return $pageURL;
1157
        }
1158
1159
        $pageURL = 'http';
1160
        if ('on' == $request->server->get('HTTPS')) {
1161
            $pageURL .= 's';
1162
        }
1163
        $pageURL .= '://';
1164
1165
        if (!in_array((int) $request->server->get('SERVER_PORT', 80), [80, 8080, 443])) {
1166
            return $pageURL.$request->server->get('SERVER_NAME').':'.$request->server->get('SERVER_PORT').
1167
                $request->server->get('REQUEST_URI');
1168
        }
1169
1170
        return $pageURL.$request->server->get('SERVER_NAME').$request->server->get('REQUEST_URI');
1171
    }
1172
1173
    /*
1174
     * Cleans query params saving url values.
1175
     *
1176
     * @param $query array
1177
     *
1178
     * @return array
1179
     */
1180
    private function cleanQuery($query)
1181
    {
1182
        foreach ($query as $key => $value) {
1183
            if (filter_var($value, FILTER_VALIDATE_URL)) {
1184
                $query[$key] = InputHelper::url($value);
1185
            } else {
1186
                $query[$key] = InputHelper::clean($value);
1187
            }
1188
        }
1189
1190
        return $query;
1191
    }
1192
}
1193