Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
73 / 73
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
TrackerService
100.00% covered (success)
100.00%
73 / 73
100.00% covered (success)
100.00%
8 / 8
14
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVisitId
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getCurrentSession
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getCurrentSessionByConfigId
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 doReturningVisit
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 writeLog
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 updateVisit
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 generateConfigId
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Drupal\visitors\Service;
4
5use Drupal\Core\Database\Connection;
6use Drupal\visitors\VisitorsTrackerInterface;
7
8/**
9 * Tracker for web analytics.
10 */
11class TrackerService implements VisitorsTrackerInterface {
12
13  /**
14   * The database connection.
15   *
16   * @var \Drupal\Core\Database\Connection
17   */
18  protected $database;
19
20  /**
21   * Tracks visits and events.
22   *
23   * @param \Drupal\Core\Database\Connection $database
24   *   The database connection.
25   */
26  public function __construct(Connection $database) {
27    $this->database = $database;
28  }
29
30  /**
31   * {@inheritdoc}
32   */
33  public function getVisitId(array $fields, int $request_time): int {
34
35    $fields['config_id'] = $this->generateConfigId($fields);
36
37    $ago = $request_time - self::SESSION_TIMEOUT;
38
39    $id = $this->getCurrentSession($fields, $ago);
40    if ($id) {
41      return $id;
42    }
43
44    $this->doReturningVisit($fields);
45
46    $id = $this->database->insert('visitors_visit')
47      ->fields($fields)
48      ->execute();
49
50    return $id;
51  }
52
53  /**
54   * Get the current session id.
55   *
56   * @param array $fields
57   *   The fields to get the session id from.
58   * @param int $ago
59   *   The time to check for the session id.
60   */
61  protected function getCurrentSession(array $fields, int $ago): ?int {
62
63    if (empty($fields['visitor_id'])) {
64      return $this->getCurrentSessionByConfigId($fields, $ago);
65    }
66
67    $current_session = $this->database->select('visitors_visit', 'vv');
68    $current_session->fields('vv', ['id']);
69    $current_session->condition('vv.visitor_id', $fields['visitor_id']);
70    $current_session->condition('vv.exit_time', $ago, '>');
71
72    $id = $current_session->execute()->fetchField();
73
74    return $id;
75  }
76
77  /**
78   * Get the current session id by config id.
79   *
80   * @param array $fields
81   *   The fields to get the session id from.
82   * @param int $ago
83   *   The time to check for the session id.
84   */
85  protected function getCurrentSessionByConfigId(array $fields, int $ago): ?int {
86    if (empty($fields['config_id'])) {
87      return NULL;
88    }
89
90    $current_session = $this->database->select('visitors_visit', 'vv');
91    $current_session->fields('vv', ['id']);
92    $current_session->condition('vv.config_id', $fields['config_id']);
93    $current_session->condition('vv.location_ip', $fields['location_ip']);
94    $current_session->condition('vv.exit_time', $ago, '>');
95
96    $id = $current_session->execute()->fetchField();
97
98    return $id;
99  }
100
101  /**
102   * Check if the visitor has visited before.
103   *
104   * If the visitor has visited before, update the visit count and set the
105   * returning flag.
106   *
107   * @param array $fields
108   *   The fields to check for the visitor.
109   */
110  protected function doReturningVisit(&$fields): void {
111    if (empty($fields['visitor_id'])) {
112      return;
113    }
114
115    $returning_visitor = $this->database->select('visitors_visit', 'vv2');
116    $returning_visitor->fields('vv2', ['visit_count'])
117      ->condition('vv2.visitor_id', $fields['visitor_id'])
118      ->orderBy('vv2.exit_time', 'DESC')
119      ->range(0, 1);
120
121    $visit_count = $returning_visitor->execute()->fetchField();
122
123    if ($visit_count !== FALSE) {
124      $fields['visit_count'] = $visit_count + 1;
125      $fields['returning'] = 1;
126    }
127  }
128
129  /**
130   * {@inheritdoc}
131   */
132  public function writeLog(array $fields): int {
133    $id = $this->database->insert('visitors_event')
134      ->fields($fields)
135      ->execute();
136
137    return $id;
138  }
139
140  /**
141   * {@inheritdoc}
142   */
143  public function updateVisit(int $visit_id, int $log_id, int $exit_time, ?int $uid) {
144
145    $update = $this->database->update('visitors_visit');
146    $update->fields([
147      'exit_time' => $exit_time,
148      'exit' => $log_id,
149    ])
150      // Expression to update the total time of the visit.
151      ->expression('total_time', ':now - entry_time', [':now' => $exit_time])
152      ->expression('page_count', '[page_count] + 1')
153      ->expression('entry', 'COALESCE(entry, :log)', [':log' => $log_id])
154      ->expression('uid', 'COALESCE(uid, :uid)', [':uid' => $uid])
155      ->condition('id', $visit_id)
156      ->execute();
157
158  }
159
160  /**
161   * Generate a config id.
162   *
163   * @param array $fields
164   *   The fields to generate the config id from.
165   *
166   * @return string
167   *   The generated config id 16 characters.
168   */
169  protected function generateConfigId($fields) {
170    $os = $fields['bot'] ? 'bot' : $fields['config_os'];
171    $config_string =
172      $os
173      . $fields['config_browser_name']
174      . $fields['config_browser_version']
175      . $fields['config_flash']
176      . $fields['config_java']
177      . $fields['config_quicktime']
178      . $fields['config_realplayer']
179      . $fields['config_pdf']
180      . $fields['config_windowsmedia']
181      . $fields['config_silverlight']
182      . $fields['config_cookie']
183      . $fields['config_resolution']
184      . $fields['location_browser_lang'];
185
186    $sha = sha1($config_string);
187    $config_id = substr($sha, 0, 16);
188
189    return $config_id;
190  }
191
192}