Issues (3627)

app/bundles/PageBundle/Entity/HitRepository.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\PageBundle\Entity;
13
14
use Mautic\CoreBundle\Entity\CommonRepository;
15
use Mautic\CoreBundle\Helper\DateTimeHelper;
16
use Mautic\LeadBundle\Entity\Lead;
17
use Mautic\LeadBundle\Entity\TimelineTrait;
18
19
/**
20
 * Class HitRepository.
21
 */
22
class HitRepository extends CommonRepository
23
{
24
    use TimelineTrait;
25
26
    /**
27
     * Determine if the page hit is a unique.
28
     *
29
     * @param Page|Redirect $page
30
     * @param string        $trackingId
31
     * @param Lead          $lead
32
     *
33
     * @return bool
34
     */
35
    public function isUniquePageHit($page, $trackingId, Lead $lead = null)
36
    {
37
        $q  = $this->getEntityManager()->getConnection()->createQueryBuilder();
38
        $q2 = $this->getEntityManager()->getConnection()->createQueryBuilder();
39
40
        $q2->select('null')
41
            ->from(MAUTIC_TABLE_PREFIX.'page_hits', 'h');
42
43
        $expr = $q2->expr()->andX();
44
45
        // If we know the lead, use that to determine uniqueness
46
        if (null !== $lead && $lead->getId()) {
47
            $expr->add(
48
                $q2->expr()->eq('h.lead_id', $lead->getId())
49
            );
50
        } else {
51
            $expr->add(
52
                $q2->expr()->eq('h.tracking_id', ':id')
53
            );
54
            $q->setParameter('id', $trackingId);
55
        }
56
57
        if ($page instanceof Page) {
58
            $expr->add(
59
                $q2->expr()->eq('h.page_id', $page->getId())
60
            );
61
        } elseif ($page instanceof Redirect) {
0 ignored issues
show
$page is always a sub-type of Mautic\PageBundle\Entity\Redirect.
Loading history...
62
            $expr->add(
63
                $q2->expr()->eq('h.redirect_id', $page->getId())
64
            );
65
        }
66
67
        $q2->where($expr);
68
69
        $q->select('u.is_unique')
70
            ->from(sprintf('(SELECT (NOT EXISTS (%s)) is_unique)', $q2->getSQL()), 'u');
71
72
        return (bool) $q->execute()->fetchColumn();
73
    }
74
75
    /**
76
     * Get a lead's page hits.
77
     *
78
     * @param int|null $leadId
79
     *
80
     * @return array
81
     */
82
    public function getLeadHits($leadId = null, array $options = [])
83
    {
84
        $query = $this->getEntityManager()->getConnection()->createQueryBuilder();
85
86
        $query->select('h.id as hitId, h.page_id, h.user_agent as userAgent, h.date_hit as dateHit, h.date_left as dateLeft, h.referer, h.source, h.source_id as sourceId, h.url, h.url_title as urlTitle, h.query, ds.client_info as clientInfo, ds.device, ds.device_os_name as deviceOsName, ds.device_brand as deviceBrand, ds.device_model as deviceModel, h.lead_id')
87
            ->from(MAUTIC_TABLE_PREFIX.'page_hits', 'h')
88
            ->leftJoin('h', MAUTIC_TABLE_PREFIX.'pages', 'p', 'h.page_id = p.id');
89
90
        if ($leadId) {
91
            $query->where('h.lead_id = '.(int) $leadId);
92
        }
93
94
        if (isset($options['search']) && $options['search']) {
95
            $query->andWhere($query->expr()->like('p.title', $query->expr()->literal('%'.$options['search'].'%')));
96
        }
97
98
        $query->leftjoin('h', MAUTIC_TABLE_PREFIX.'lead_devices', 'ds', 'ds.id = h.device_id');
99
100
        if (isset($options['url']) && $options['url']) {
101
            $query->andWhere($query->expr()->eq('h.url', $query->expr()->literal($options['url'])));
102
        }
103
104
        return $this->getTimelineResults($query, $options, 'p.title', 'h.date_hit', ['query'], ['dateHit', 'dateLeft']);
105
    }
106
107
    /**
108
     * @param      $source
109
     * @param null $sourceId
110
     * @param null $fromDate
111
     *
112
     * @return array
113
     */
114
    public function getHitCountForSource($source, $sourceId = null, $fromDate = null, $code = 200)
115
    {
116
        $query = $this->createQueryBuilder('h');
117
        $query->select('count(distinct(h.trackingId)) as "hitCount"');
118
        $query->andWhere($query->expr()->eq('h.source', $query->expr()->literal($source)));
119
120
        if (null != $sourceId) {
121
            if (is_array($sourceId)) {
122
                $query->andWhere($query->expr()->in('h.sourceId', ':sourceIds'))
123
                    ->setParameter('sourceIds', $sourceId);
124
            } else {
125
                $query->andWhere($query->expr()->eq('h.sourceId', (int) $sourceId));
126
            }
127
        }
128
129
        if (null != $fromDate) {
130
            $query->andwhere($query->expr()->gte('h.dateHit', ':date'))
131
                ->setParameter('date', $fromDate);
132
        }
133
134
        $query->andWhere($query->expr()->eq('h.code', (int) $code));
135
136
        return $hits = $query->getQuery()->getArrayResult();
137
    }
138
139
    /**
140
     * Get an array of hits via an email clickthrough.
141
     *
142
     * @param           $emailIds
143
     * @param \DateTime $fromDate
144
     * @param int       $code
145
     *
146
     * @return array
147
     */
148
    public function getEmailClickthroughHitCount($emailIds, \DateTime $fromDate = null, $code = 200)
149
    {
150
        $q = $this->_em->getConnection()->createQueryBuilder();
151
152
        if (!is_array($emailIds)) {
153
            $emailIds = [$emailIds];
154
        }
155
156
        $q->select('count(distinct(h.tracking_id)) as hit_count, h.email_id')
157
            ->from(MAUTIC_TABLE_PREFIX.'page_hits', 'h')
158
            ->where($q->expr()->in('h.email_id', $emailIds))
159
            ->groupBy('h.email_id');
160
161
        if (null != $fromDate) {
162
            $dateHelper = new DateTimeHelper($fromDate);
163
            $q->andwhere($q->expr()->gte('h.date_hit', ':date'))
164
                ->setParameter('date', $dateHelper->toUtcString());
165
        }
166
167
        $q->andWhere($q->expr()->eq('h.code', (int) $code));
168
169
        $results = $q->execute()->fetchAll();
170
171
        $hits = [];
172
        foreach ($results as $r) {
173
            $hits[$r['email_id']] = $r['hit_count'];
174
        }
175
176
        return $hits;
177
    }
178
179
    /**
180
     * Count returning IP addresses.
181
     *
182
     * @return int
183
     */
184
    public function countReturningIp()
185
    {
186
        $q = $this->createQueryBuilder('h');
187
        $q->select('COUNT(h.ipAddress) as returning')
188
            ->groupBy('h.ipAddress')
189
            ->having($q->expr()->gt('COUNT(h.ipAddress)', 1));
190
        $results = $q->getQuery()->getResult();
191
192
        return count($results);
193
    }
194
195
    /**
196
     * Count email clickthrough.
197
     *
198
     * @return int
199
     */
200
    public function countEmailClickthrough()
201
    {
202
        $q = $this->createQueryBuilder('h');
203
        $q->select('COUNT(h.email) as clicks');
204
        $results = $q->getQuery()->getSingleResult();
205
206
        return $results['clicks'];
207
    }
208
209
    /**
210
     * Count how many visitors hit some page in last X $seconds.
211
     *
212
     * @param int  $seconds
213
     * @param bool $notLeft
214
     *
215
     * @return int
216
     */
217
    public function countVisitors($seconds = 60, $notLeft = false)
218
    {
219
        $now         = new \DateTime();
220
        $viewingTime = new \DateInterval('PT'.$seconds.'S');
221
        $now->sub($viewingTime);
222
        $query = $this->createQueryBuilder('h');
223
224
        $query->select('count(h.code) as visitors');
225
226
        if ($seconds) {
227
            $query->where($query->expr()->gte('h.dateHit', ':date'))
228
                ->setParameter('date', $now);
229
        }
230
231
        if ($notLeft) {
232
            $query->andWhere($query->expr()->isNull('h.dateLeft'));
233
        }
234
235
        $result = $query->getQuery()->getSingleResult();
236
237
        if (!isset($result['visitors'])) {
238
            return 0;
239
        }
240
241
        return (int) $result['visitors'];
242
    }
243
244
    /**
245
     * Get the latest hit.
246
     *
247
     * @param array $options
248
     *
249
     * @return \DateTime
250
     */
251
    public function getLatestHit($options)
252
    {
253
        $sq = $this->_em->getConnection()->createQueryBuilder();
254
        $sq->select('h.date_hit latest_hit')
255
            ->from(MAUTIC_TABLE_PREFIX.'page_hits', 'h');
256
257
        if (isset($options['leadId'])) {
258
            $sq->andWhere(
259
                $sq->expr()->eq('h.lead_id', $options['leadId'])
260
            );
261
        }
262
        if (isset($options['urls']) && $options['urls']) {
263
            $inUrls = (!is_array($options['urls'])) ? [$options['urls']] : $options['urls'];
264
            foreach ($inUrls as $k => $u) {
265
                $sq->andWhere($sq->expr()->like('h.url', ':url_'.$k))
266
                    ->setParameter('url_'.$k, $u);
267
            }
268
        }
269
        if (isset($options['second_to_last'])) {
270
            $sq->andWhere($sq->expr()->neq('h.id', $options['second_to_last']));
271
        } else {
272
            $sq->orderBy('h.date_hit', 'DESC limit 1');
273
        }
274
        $result = $sq->execute()->fetch();
275
276
        return new \DateTime($result['latest_hit'], new \DateTimeZone('UTC'));
277
    }
278
279
    /**
280
     * Get the number of bounces.
281
     *
282
     * @param array|string $pageIds
283
     * @param \DateTime    $fromDate
284
     * @param bool         $isVariantCheck
285
     *
286
     * @return array
287
     */
288
    public function getBounces($pageIds, \DateTime $fromDate = null, $isVariantCheck = false)
289
    {
290
        $inOrEq = (!is_array($pageIds)) ? 'eq' : 'in';
291
292
        $hitsColumn = ($isVariantCheck) ? 'variant_hits' : 'unique_hits';
293
        $q          = $this->getEntityManager()->getConnection()->createQueryBuilder();
294
        $pages      = $q->select("p.id, p.$hitsColumn as totalHits, p.title")
295
            ->from(MAUTIC_TABLE_PREFIX.'pages', 'p')
296
            ->where($q->expr()->$inOrEq('p.id', $pageIds))
297
            ->execute()
298
            ->fetchAll();
299
300
        $return = [];
301
        foreach ($pages as $p) {
302
            $return[$p['id']] = [
303
                'totalHits' => (int) $p['totalHits'],
304
                'bounces'   => 0,
305
                'rate'      => 0,
306
                'title'     => $p['title'],
307
            ];
308
        }
309
310
        // Get the total number of bounces - simplified query for if date_left is null, it'll more than likely be a bounce or
311
        // else we would have recorded the date_left on a subsequent page hit
312
        $q    = $this->getEntityManager()->getConnection()->createQueryBuilder();
313
        $expr = $q->expr()->andX(
314
            $q->expr()->$inOrEq('h.page_id', $pageIds),
315
            $q->expr()->eq('h.code', 200),
316
            $q->expr()->isNull('h.date_left')
317
        );
318
319
        if (null !== $fromDate) {
320
            //make sure the date is UTC
321
            $dt = new DateTimeHelper($fromDate, 'Y-m-d H:i:s', 'local');
322
            $expr->add(
323
                $q->expr()->gte('h.date_hit', $q->expr()->literal($dt->toUtcString()))
324
            );
325
        }
326
327
        $q->select('count(*) as bounces, h.page_id')
328
            ->from(MAUTIC_TABLE_PREFIX.'page_hits', 'h')
329
            ->where($expr)
330
            ->groupBy('h.page_id');
331
332
        $results = $q->execute()->fetchAll();
333
334
        foreach ($results as $p) {
335
            $return[$p['page_id']]['bounces'] = (int) $p['bounces'];
336
            $return[$p['page_id']]['rate']    = ($return[$p['page_id']]['totalHits']) ? round(
337
                ($p['bounces'] / $return[$p['page_id']]['totalHits']) * 100,
338
                2
339
            ) : 0;
340
        }
341
342
        return (!is_array($pageIds)) ? $return[$pageIds] : $return;
343
    }
344
345
    /**
346
     * Get array of dwell time labels with ranges.
347
     *
348
     * @return array
349
     */
350
    public function getDwellTimeLabels()
351
    {
352
        return [
353
            [
354
                'label' => '< 1m',
355
                'from'  => 0,
356
                'till'  => 60,
357
            ],
358
            [
359
                'label' => '1 - 5m',
360
                'from'  => 60,
361
                'till'  => 300,
362
            ],
363
            [
364
                'label' => '5 - 10m',
365
                'value' => 0,
366
                'from'  => 300,
367
                'till'  => 600,
368
            ],
369
            [
370
                'label' => '> 10m',
371
                'from'  => 600,
372
                'till'  => 999999,
373
            ],
374
        ];
375
    }
376
377
    /**
378
     * Get the dwell times for bunch of pages.
379
     *
380
     * @return array
381
     */
382
    public function getDwellTimesForPages(array $pageIds, array $options)
383
    {
384
        $q = $this->_em->getConnection()->createQueryBuilder();
385
        $q->from(MAUTIC_TABLE_PREFIX.'page_hits', 'ph')
386
            ->leftJoin('ph', MAUTIC_TABLE_PREFIX.'pages', 'p', 'ph.page_id = p.id')
387
            ->select('ph.page_id, ph.date_hit, ph.date_left, p.title')
388
            ->orderBy('ph.date_hit', 'ASC')
389
            ->andWhere(
390
                $q->expr()->andX(
391
                    $q->expr()->in('ph.page_id', $pageIds)
392
                )
393
            );
394
395
        if (isset($options['fromDate']) && null !== $options['fromDate']) {
396
            //make sure the date is UTC
397
            $dt = new DateTimeHelper($options['fromDate']);
398
            $q->andWhere(
399
                $q->expr()->gte('ph.date_hit', $q->expr()->literal($dt->toUtcString()))
400
            );
401
        }
402
403
        $results = $q->execute()->fetchAll();
404
405
        //loop to structure
406
        $times  = [];
407
        $titles = [];
408
409
        foreach ($results as $r) {
410
            $dateHit  = $r['date_hit'] ? new \DateTime($r['date_hit']) : 0;
411
            $dateLeft = $r['date_left'] ? new \DateTime($r['date_left']) : 0;
412
413
            $titles[$r['page_id']]  = $r['title'];
414
            $times[$r['page_id']][] = $dateLeft ? ($dateLeft->getTimestamp() - $dateHit->getTimestamp()) : 0;
415
        }
416
417
        //now loop to create stats
418
        $stats = [];
419
420
        foreach ($times as $pid => $time) {
421
            $stats[$pid]          = $this->countStats($time);
422
            $stats[$pid]['title'] = $titles[$pid];
423
        }
424
425
        return $stats;
426
    }
427
428
    /**
429
     * Get the dwell times for bunch of URLs.
430
     *
431
     * @param string $url
432
     *
433
     * @return array
434
     */
435
    public function getDwellTimesForUrl($url, array $options)
436
    {
437
        $q = $this->_em->getConnection()->createQueryBuilder();
438
        $q->from(MAUTIC_TABLE_PREFIX.'page_hits', 'ph')
439
            ->leftJoin('ph', MAUTIC_TABLE_PREFIX.'pages', 'p', 'ph.page_id = p.id')
440
            ->select('ph.id, ph.page_id, ph.date_hit, ph.date_left, ph.tracking_id, ph.page_language, p.title')
441
            ->orderBy('ph.date_hit', 'ASC')
442
            ->andWhere($q->expr()->like('ph.url', ':url'))
443
            ->setParameter('url', $url);
444
445
        if (isset($options['leadId']) && $options['leadId']) {
446
            $q->andWhere(
447
                $q->expr()->eq('ph.lead_id', (int) $options['leadId'])
448
            );
449
        }
450
451
        $results = $q->execute()->fetchAll();
452
453
        $times = [];
454
455
        foreach ($results as $r) {
456
            $dateHit  = $r['date_hit'] ? new \DateTime($r['date_hit']) : 0;
457
            $dateLeft = $r['date_left'] ? new \DateTime($r['date_left']) : 0;
458
            $times[]  = $dateLeft ? ($dateLeft->getTimestamp() - $dateHit->getTimestamp()) : 0;
459
        }
460
461
        return $this->countStats($times);
462
    }
463
464
    /**
465
     * Count stats from hit times.
466
     *
467
     * @param array $times
468
     *
469
     * @return array
470
     */
471
    public function countStats($times)
472
    {
473
        return [
474
            'sum'     => array_sum($times),
475
            'min'     => count($times) ? min($times) : 0,
476
            'max'     => count($times) ? max($times) : 0,
477
            'average' => count($times) ? round(array_sum($times) / count($times)) : 0,
478
            'count'   => count($times),
479
        ];
480
    }
481
482
    /**
483
     * Update a hit with the the time the user left.
484
     *
485
     * @param int $lastHitId
486
     */
487
    public function updateHitDateLeft($lastHitId)
488
    {
489
        $dt = new DateTimeHelper();
490
        $q  = $this->_em->getConnection()->createQueryBuilder();
491
        $q->update(MAUTIC_TABLE_PREFIX.'page_hits')
492
            ->set('date_left', ':datetime')
493
            ->where('id = '.(int) $lastHitId)
494
            ->setParameter('datetime', $dt->toUtcString());
495
        $q->execute();
496
    }
497
498
    /**
499
     * Get list of referers ordered by it's count.
500
     *
501
     * @param \Doctrine\DBAL\Query\QueryBuilder $query
502
     * @param int                               $limit
503
     * @param int                               $offset
504
     *
505
     * @return array
506
     *
507
     * @throws \Doctrine\ORM\NoResultException
508
     * @throws \Doctrine\ORM\NonUniqueResultException
509
     */
510
    public function getReferers($query, $limit = 10, $offset = 0)
511
    {
512
        $query->select('ph.referer, count(ph.referer) as sessions')
513
            ->groupBy('ph.referer')
514
            ->orderBy('sessions', 'DESC')
515
            ->setMaxResults($limit)
516
            ->setFirstResult($offset);
517
518
        return $query->execute()->fetchAll();
519
    }
520
521
    /**
522
     * Get list of referers ordered by it's count.
523
     *
524
     * @param \Doctrine\DBAL\Query\QueryBuilder $query
525
     * @param int                               $limit
526
     * @param int                               $offset
527
     * @param string                            $column
528
     * @param string                            $as
529
     *
530
     * @return array
531
     *
532
     * @throws \Doctrine\ORM\NoResultException
533
     * @throws \Doctrine\ORM\NonUniqueResultException
534
     */
535
    public function getMostVisited($query, $limit = 10, $offset = 0, $column = 'p.hits', $as = '')
536
    {
537
        if ($as) {
538
            $as = ' as "'.$as.'"';
539
        }
540
541
        $query->select('p.title, p.id, '.$column.$as)
542
            ->groupBy('p.id, p.title, '.$column)
543
            ->orderBy($column, 'DESC')
544
            ->setMaxResults($limit)
545
            ->setFirstResult($offset);
546
547
        return $query->execute()->fetchAll();
548
    }
549
550
    /**
551
     * @param $leadId
552
     * @param $newTrackingId
553
     * @param $oldTrackingId
554
     */
555
    public function updateLeadByTrackingId($leadId, $newTrackingId, $oldTrackingId)
556
    {
557
        $q = $this->_em->getConnection()->createQueryBuilder();
558
        $q->update(MAUTIC_TABLE_PREFIX.'page_hits')
559
            ->set('lead_id', (int) $leadId)
560
            ->set('tracking_id', ':newTrackingId')
561
            ->where(
562
                $q->expr()->eq('tracking_id', ':oldTrackingId')
563
            )
564
            ->setParameters([
565
                'newTrackingId' => $newTrackingId,
566
                'oldTrackingId' => $oldTrackingId,
567
            ])
568
            ->execute();
569
    }
570
571
    /**
572
     * Updates lead ID (e.g. after a lead merge).
573
     *
574
     * @param $fromLeadId
575
     * @param $toLeadId
576
     */
577
    public function updateLead($fromLeadId, $toLeadId)
578
    {
579
        $q = $this->_em->getConnection()->createQueryBuilder();
580
        $q->update(MAUTIC_TABLE_PREFIX.'page_hits')
581
            ->set('lead_id', (int) $toLeadId)
582
            ->where('lead_id = '.(int) $fromLeadId)
583
            ->execute();
584
    }
585
}
586