Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.48% covered (success)
96.48%
849 / 880
90.91% covered (success)
90.91%
10 / 11
CRAP
n/a
0 / 0
visitors_help
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
visitors_cron
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
visitors_page_attachments
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
90
visitors_form_user_form_alter
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
5
visitors_user_profile_form_submit
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
visitors_node_links_alter
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
visitors_entity_delete
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
visitors_ranking
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
visitors_views_data
100.00% covered (success)
100.00%
721 / 721
100.00% covered (success)
100.00%
1 / 1
7
visitors_token_info
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
visitors_tokens
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
12
1<?php
2
3/**
4 * @file
5 * Logs visitors for your site.
6 */
7
8use Drupal\Core\Access\AccessResult;
9use Drupal\Core\Entity\ContentEntityInterface;
10use Drupal\Core\Entity\EntityInterface;
11use Drupal\Core\Form\FormStateInterface;
12use Drupal\Core\Render\BubbleableMetadata;
13use Drupal\Core\Utility\Error;
14use Drupal\node\NodeInterface;
15use Drupal\visitors\VisitorsVisibilityInterface;
16
17/**
18 * Implements hook_help().
19 */
20function visitors_help($route_name, $route_match) {
21  switch ($route_name) {
22    case 'help.page.visitors':
23      $help = '<p><a href="https://git.drupalcode.org/project/visitors/-/commits/8.x-2.x"><img alt="coverage report" src="https://git.drupalcode.org/project/visitors/badges/8.x-2.x/coverage.svg" /></a> &nbsp;';
24      $help .= '<a href="https://git.drupalcode.org/project/visitors/-/commits/8.x-2.x"><img alt="pipeline status" src="https://git.drupalcode.org/project/visitors/badges/8.x-2.x/pipeline.svg" /></a> &nbsp;';
25      $help .= '<a href="https://www.drupal.org/project/visitors">Homepage</a> &nbsp;';
26      $help .= '<a href="https://www.drupal.org/project/issues/visitors?version=any_8.x-">Issues</a></p>';
27      $help .= '<p>'
28        . t('The Visitors module logs all visitors to your site and provides various statistics about them.')
29        . '</p>';
30
31      return [
32        '#title' => t('Visitors'),
33        'description' => [
34          '#markup' => $help,
35        ],
36      ];
37
38  }
39}
40
41/**
42 * Implements hook_cron().
43 */
44function visitors_cron(): void {
45  \Drupal::service('visitors.cron')->execute();
46}
47
48/**
49 * Implements hook_page_attachments().
50 */
51function visitors_page_attachments(array &$page) {
52  $required_permissions = ['access visitors', 'access toolbar'];
53  $current_user = \Drupal::currentUser();
54  $access = AccessResult::allowedIfHasPermissions($current_user, $required_permissions);
55  if ($access->isAllowed()) {
56    $page['#attached']['library'][] = 'visitors/menu';
57  }
58  $page['#cache']['tags'][] = 'user:' . $current_user->id();
59  $page['#cache']['tags'][] = 'config:visitors.config';
60  $page['#cache']['contexts'][] = 'user';
61
62  try {
63    /** @var \Drupal\visitors\VisitorsVisibilityInterface $visibility_service */
64    $visibility_service = \Drupal::service('visitors.visibility');
65    if (!$visibility_service->isVisible()) {
66      return NULL;
67    }
68
69    $route = \Drupal::routeMatch()->getRouteName();
70    $base_path = \Drupal::request()->getBasePath();
71    $module_path = \Drupal::service('module_handler')->getModule('visitors')->getPath();
72
73    $page['#attached']['drupalSettings']['visitors']['module'] = "$base_path/$module_path";
74    $page['#attached']['drupalSettings']['visitors']['route'] = $route;
75    $page['#attached']['drupalSettings']['visitors']['server'] = gethostname();
76    $page['#attached']['library'][] = 'visitors/visitors';
77
78    $route_array = explode('.', $route);
79    if (count($route_array) == 3 && $route_array[0] == 'entity' && $route_array[2] == 'canonical') {
80      $entity_type = $route_array[1];
81      $settings = \Drupal::config('visitors.config');
82      $entity_types = $settings->get('counter.entity_types') ?? [];
83      $is_disabled_or_not_has_entity_types = !$settings->get('counter.enabled') || !in_array($entity_type, $entity_types);
84      if ($is_disabled_or_not_has_entity_types) {
85        return NULL;
86      }
87      $entity_id = \Drupal::routeMatch()->getParameter($entity_type)->id();
88      $page['#attached']['drupalSettings']['visitors']['counter'] = "$entity_type:$entity_id";
89    }
90
91  }
92  catch (\Exception $e) {
93    $logger = \Drupal::logger('visitors');
94    Error::logException($logger, $e);
95  }
96
97}
98
99/**
100 * Implements hook_form_FORM_ID_alter().
101 *
102 * Allow users to decide if tracking code will be added to pages or not.
103 */
104function visitors_form_user_form_alter(&$form, FormStateInterface $form_state) {
105
106  $config = \Drupal::config('visitors.config');
107  $visibility_users = $config->get('visibility.user_account_mode');
108
109  if ($visibility_users == VisitorsVisibilityInterface::USER_NO_PERSONALIZATION) {
110    return;
111  }
112
113  /** @var \Drupal\user\AccountForm $user_form */
114  $user_form = $form_state->getFormObject();
115  /** @var \Drupal\user\UserInterface $account */
116  $account = $user_form->getEntity();
117
118  if (!$account->hasPermission('opt-out of visitors tracking')) {
119    return;
120  }
121
122  $account_data_visitors = \Drupal::service('user.data')->get('visitors', $account->id());
123
124  $form['visitors'] = [
125    '#type' => 'details',
126    '#title' => t('Visitors settings'),
127    '#weight' => 3,
128    '#open' => TRUE,
129  ];
130  $description = '';
131  switch ($visibility_users) {
132    case VisitorsVisibilityInterface::USER_OPT_OUT:
133      $description = t('Users are tracked by default, but you are able to opt out.');
134      break;
135
136    case VisitorsVisibilityInterface::USER_OPT_IN:
137      $description = t('Users are <em>not</em> tracked by default, but you are able to opt in.');
138      break;
139  }
140
141  $default_value = $account_data_visitors['user_account_users'] ?? $visibility_users;
142  $form['visitors']['user_account_users'] = [
143    '#type' => 'checkbox',
144    '#title' => t('Enable user tracking'),
145    '#description' => $description,
146    '#default_value' => $default_value,
147  ];
148
149  // Custom submit handler.
150  $form['actions']['submit']['#submit'][] = 'visitors_user_profile_form_submit';
151
152}
153
154/**
155 * Submit callback for user profile form to save the Visitor setting.
156 */
157function visitors_user_profile_form_submit($form, FormStateInterface $form_state) {
158  if (!$form_state->hasValue('user_account_users')) {
159    return;
160  }
161  /** @var \Drupal\user\AccountForm $user_form */
162  $user_form = $form_state->getFormObject();
163  /** @var \Drupal\user\UserInterface $account */
164  $account = $user_form->getEntity();
165
166  $value = (int) $form_state->getValue('user_account_users');
167  \Drupal::service('user.data')
168    ->set('visitors', $account->id(), 'user_account_users', $value);
169}
170
171/**
172 * Implements hook_node_links_alter().
173 */
174function visitors_node_links_alter(array &$links, NodeInterface $entity, array &$context) {
175  if ($context['view_mode'] == 'rss') {
176    return NULL;
177  }
178  $links['#cache']['contexts'][] = 'user.permissions';
179  if (!\Drupal::currentUser()->hasPermission('view visitors counter')) {
180    return NULL;
181  }
182  $settings = \Drupal::config('visitors.config');
183
184  $statistics = \Drupal::service('visitors.counter')->fetchView('node', $entity->id());
185  if ($statistics) {
186    $statistics_links['visitors_counter']['title'] = \Drupal::translation()
187      ->formatPlural($statistics->getTotalCount(), '1 view', '@count views');
188    $links['visitors'] = [
189      '#theme' => 'links__node__visitors',
190      '#links' => $statistics_links,
191      '#attributes' => ['class' => ['links', 'inline']],
192    ];
193  }
194  $links['#cache']['max-age'] = $settings->get('counter.display_max_age');
195
196}
197
198/**
199 * Implements hook_entity_delete().
200 */
201function visitors_entity_delete(EntityInterface $entity) {
202
203  $entity_id = $entity->id();
204  if (!is_int($entity_id)) {
205    return;
206  }
207  $entity_type = $entity->getEntityTypeId();
208
209  \Drupal::service('visitors.counter')
210    ->deleteViews($entity_type, $entity_id);
211}
212
213/**
214 * Implements hook_ranking().
215 */
216function visitors_ranking() {
217  $settings = \Drupal::config('visitors.config');
218  $is_enabled_and_has_node_entity_type = $settings->get('counter.enabled')
219    && in_array('node', $settings->get('counter.entity_types'));
220  if ($is_enabled_and_has_node_entity_type) {
221    return [
222      'views' => [
223        'title' => t('Number of views'),
224        'join' => [
225          'type' => 'LEFT',
226          'table' => 'visitors_counter',
227          'alias' => 'visitors_counter',
228          'on' => "visitors_counter.entity_id = i.sid AND visitors_counter.entity_type = 'node'",
229        ],
230        // Inverse law that maps the highest view count on the site to 1 and 0
231        // to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite
232        // in order to ensure that the :statistics_scale argument is treated as
233        // a numeric type, because the PostgreSQL PDO driver sometimes puts
234        // values in as strings instead of numbers in complex expressions like
235        // this.
236        'score' => '2.0 - 2.0 / (1.0 + visitors_counter.total * (ROUND(:statistics_scale, 4)))',
237        'arguments' => [':statistics_scale' => \Drupal::state()->get('visitors.node_counter_scale', 0)],
238      ],
239    ];
240  }
241}
242
243/**
244 * Implements hook_views_data().
245 */
246function visitors_views_data() {
247  $data = [];
248  $data['visitors_counter']['table']['group'] = t('Visitor counters');
249  $data['visitors_counter']['table']['base'] = [
250    'title' => t('Visitor Counters'),
251    'help' => t('Visitors data from visitors DB table.'),
252  ];
253  $settings = \Drupal::config('visitors.config');
254  $supported_entity_types = $settings->get('counter.entity_types') ?? [];
255  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
256    $base_table = $entity_type->getBaseTable();
257    if (!in_array($entity_type_id, $supported_entity_types) || !$entity_type->entityClassImplements(ContentEntityInterface::class) || !$base_table) {
258      continue;
259    }
260
261    $base_table = $entity_type->getDataTable() ?: $base_table;
262    $args = ['@entity_type' => $entity_type_id];
263
264    // Multilingual properties are stored in data table.
265    if (!($table = $entity_type->getDataTable())) {
266      $table = $base_table;
267    }
268    $data[$base_table]['visitors_counter'] = [
269      'title' => t('Visitors @entity_type counter', $args),
270      'help' => t('Relate all visitor counts on the @entity_type.', $args),
271      'relationship' => [
272        'group' => t('Visitor Counters'),
273        'label' => t('Visitor counters'),
274        'base' => 'visitors_counter',
275        'base field' => 'entity_id',
276        'relationship field' => $entity_type->getKey('id'),
277        'id' => 'standard',
278        'extra' => [
279          [
280            'field' => 'entity_type',
281            'value' => $entity_type_id,
282          ],
283        ],
284      ],
285    ];
286
287    $data['visitors_counter']['table']['join'][$table] = [
288      'type' => 'LEFT',
289      'left_field' => $entity_type->getKey('id'),
290      'field' => 'entity_id',
291      'extra' => [
292        [
293          'field' => 'entity_type',
294          'value' => $entity_type_id,
295        ],
296      ],
297    ];
298
299  }
300
301  $data['visitors_counter']['total'] = [
302    'title' => t('Total views'),
303    'help' => t('The total number of times the node has been viewed.'),
304    'field' => [
305      'id' => 'visitors_numeric',
306      'click sortable' => TRUE,
307    ],
308    'filter' => [
309      'id' => 'numeric',
310    ],
311    'argument' => [
312      'id' => 'numeric',
313    ],
314    'sort' => [
315      'id' => 'standard',
316    ],
317  ];
318  $data['visitors_counter']['today'] = [
319    'title' => t('Views today'),
320    'help' => t('The total number of times the node has been viewed today.'),
321    'field' => [
322      'id' => 'visitors_numeric',
323      'click sortable' => TRUE,
324    ],
325    'filter' => [
326      'id' => 'numeric',
327    ],
328    'argument' => [
329      'id' => 'numeric',
330    ],
331    'sort' => [
332      'id' => 'standard',
333    ],
334  ];
335  $data['visitors_counter']['timestamp'] = [
336    'title' => t('Most recent visit'),
337    'help' => t('The most recent time the node has been viewed.'),
338    'field' => [
339      'id' => 'visitors_counter_timestamp',
340      'click sortable' => TRUE,
341    ],
342    'filter' => [
343      'id' => 'date',
344    ],
345    'argument' => [
346      'id' => 'date',
347    ],
348    'sort' => [
349      'id' => 'standard',
350    ],
351  ];
352
353  $data['visitors']['table']['group'] = t('Visitors');
354  $data['visitors']['table']['base'] = [
355    'title' => t('Visitors'),
356    'help' => t('Visitors data from visitors DB table.'),
357  ];
358
359  $data['visitors']['visitors_id'] = [
360    'title' => t('Visitors ID'),
361    'help' => t('Visitors entry ID.'),
362    'field' => [
363      'id' => 'numeric',
364    ],
365    'sort' => [
366      'id' => 'standard',
367    ],
368    'filter' => [
369      'id' => 'numeric',
370    ],
371    'argument' => [
372      'id' => 'numeric',
373    ],
374  ];
375  $data['visitors']['visitor_id'] = [
376    'title' => t('Unique visitor'),
377    'help' => t('A unique ID for the visitor.'),
378    'field' => [
379      'id' => 'standard',
380    ],
381    'filter' => [
382      'id' => 'string',
383    ],
384    'sort' => [
385      'id' => 'standard',
386    ],
387    'argument' => [
388      'id' => 'string',
389    ],
390  ];
391  $data['visitors']['visitors_uid'] = [
392    'title' => t('Visitors UID'),
393    'help' => t('The user ID of the visitors entry.'),
394    'field' => [
395      'id' => 'standard',
396    ],
397    'relationship' => [
398      'title' => t('User'),
399      'help' => t('The user entity from the visitor entry.'),
400      'base' => 'users_field_data',
401      'base field' => 'uid',
402      'id' => 'standard',
403    ],
404    'filter' => [
405      'id' => 'numeric',
406    ],
407    'argument' => [
408      'id' => 'numeric',
409    ],
410  ];
411  $data['visitors']['visitors_date_time'] = [
412    'title' => t('Visitors Date Time'),
413    'help' => t('The timestamp from the visitors entry.'),
414    'field' => [
415      'id' => 'date',
416      'click sortable' => TRUE,
417    ],
418    'filter' => [
419      'id' => 'visitors_date',
420    ],
421  ];
422  $data['visitors']['visitors_hour'] = [
423    'title' => t('Hour'),
424    'help' => t('The hour (server) of the visit.'),
425    'field' => [
426      'id' => 'visitors_hour',
427      'field' => 'visitors_date_time',
428    ],
429    'sort' => [
430      'id' => 'visitors_timestamp',
431      'field' => 'visitors_date_time',
432    ],
433  ];
434  $data['visitors']['visitors_month'] = [
435    'title' => t('Month'),
436    'help' => t('The month of the visit.'),
437    'field' => [
438      'id' => 'visitors_month',
439      'field' => 'visitors_date_time',
440    ],
441    'sort' => [
442      'id' => 'visitors_timestamp',
443      'field' => 'visitors_date_time',
444    ],
445  ];
446  $data['visitors']['visitors_day_of_week'] = [
447    'title' => t('Day of Week'),
448    'help' => t('The day of week of the visit.'),
449    'field' => [
450      'id' => 'visitors_day_of_week',
451      'field' => 'visitors_date_time',
452    ],
453    'sort' => [
454      'id' => 'visitors_timestamp',
455      'field' => 'visitors_date_time',
456    ],
457  ];
458  $data['visitors']['visitors_day_of_month'] = [
459    'title' => t('Day of Month'),
460    'help' => t('The day of month of the visit.'),
461    'field' => [
462      'id' => 'visitors_day_of_month',
463      'field' => 'visitors_date_time',
464    ],
465    'sort' => [
466      'id' => 'visitors_timestamp',
467      'field' => 'visitors_date_time',
468    ],
469  ];
470  $data['visitors']['visitors_day'] = [
471    'title' => t('Day'),
472    'help' => t('The day of the visit.'),
473    'field' => [
474      'id' => 'visitors_day',
475      'field' => 'visitors_date_time',
476    ],
477    'sort' => [
478      'id' => 'visitors_timestamp',
479      'field' => 'visitors_date_time',
480    ],
481  ];
482  $data['visitors']['visitors_week'] = [
483    'title' => t('Week'),
484    'help' => t('The week of the visit.'),
485    'field' => [
486      'id' => 'visitors_week',
487      'field' => 'visitors_date_time',
488    ],
489    'sort' => [
490      'id' => 'visitors_timestamp',
491      'field' => 'visitors_date_time',
492    ],
493  ];
494  $data['visitors']['visitor_localtime'] = [
495    'title' => t('Visitor Hour'),
496    'help' => t('The hour (client) of the visit.'),
497    'field' => [
498      'id' => 'visitors_local_hour',
499      'field' => 'visitor_localtime',
500    ],
501    'sort' => [
502      'id' => 'visitors_timestamp',
503      'field' => 'visitors_date_time',
504    ],
505  ];
506  $data['visitors']['visitors_ip'] = [
507    'title' => t('Visitors IP'),
508    'help' => t('The IP of the visitors entry.'),
509    'field' => [
510      'id' => 'standard',
511    ],
512    'filter' => [
513      'id' => 'string',
514    ],
515    'argument' => [
516      'id' => 'string',
517    ],
518  ];
519  $data['visitors']['server'] = [
520    'title' => t('Server'),
521    'help' => t('The server that generated the response.'),
522    'field' => [
523      'id' => 'standard',
524    ],
525    'filter' => [
526      'id' => 'string',
527    ],
528    'argument' => [
529      'id' => 'string',
530    ],
531  ];
532  $data['visitors']['visitors_url'] = [
533    'title' => t('Visitors URL'),
534    'help' => t('The URL of the visitors entry.'),
535    'field' => [
536      'id' => 'standard',
537    ],
538    'filter' => [
539      'id' => 'string',
540    ],
541  ];
542  $data['visitors']['visitors_referer'] = [
543    'title' => t('Visitors referer'),
544    'help' => t('The referer of the visitors entry.'),
545    'field' => [
546      'id' => 'standard',
547    ],
548    'filter' => [
549      'id' => 'string',
550    ],
551  ];
552  $data['visitors']['visitors_path'] = [
553    'title' => t('Visitors path'),
554    'help' => t('The path of the visitors entry.'),
555    'field' => [
556      'id' => 'standard',
557    ],
558    'filter' => [
559      'id' => 'string',
560    ],
561    'argument' => [
562      'id' => 'string',
563    ],
564  ];
565  $data['visitors']['route'] = [
566    'title' => t('Route'),
567    'help' => t('The route of the visitors entry.'),
568    'field' => [
569      'id' => 'standard',
570    ],
571    'filter' => [
572      'id' => 'string',
573    ],
574    'argument' => [
575      'id' => 'string',
576    ],
577  ];
578  $data['visitors']['visitors_title'] = [
579    'title' => t('Visitors title'),
580    'help' => t('The title of the visitors entry.'),
581    'field' => [
582      'id' => 'standard',
583    ],
584    'filter' => [
585      'id' => 'string',
586    ],
587  ];
588  $data['visitors']['visitors_user_agent'] = [
589    'title' => t('Visitors user agent'),
590    'help' => t('The user agent of the visitors entry.'),
591    'field' => [
592      'id' => 'standard',
593    ],
594    'filter' => [
595      'id' => 'string',
596    ],
597  ];
598  $data['visitors']['config_resolution'] = [
599    'title' => t('Resolution'),
600    'help' => t("The visitor's screen resolution."),
601    'field' => [
602      'id' => 'standard',
603    ],
604    'filter' => [
605      'id' => 'string',
606    ],
607    'argument' => [
608      'id' => 'string',
609    ],
610  ];
611  $data['visitors']['config_pdf'] = [
612    'title' => t('PDF Plugin'),
613    'help' => t("The visitor's browser supports PDFs."),
614    'field' => [
615      'id' => 'visitors_pdf',
616    ],
617    'filter' => [
618      'id' => 'boolean',
619    ],
620  ];
621  $data['visitors']['config_flash'] = [
622    'title' => t('Flash Plugin'),
623    'help' => t("The visitor's browser supports Flash."),
624    'field' => [
625      'id' => 'visitors_flash',
626    ],
627    'filter' => [
628      'id' => 'boolean',
629    ],
630  ];
631  $data['visitors']['config_java'] = [
632    'title' => t('Java Plugin'),
633    'help' => t("The visitor's browser supports Java."),
634    'field' => [
635      'id' => 'visitors_java',
636    ],
637    'filter' => [
638      'id' => 'boolean',
639    ],
640  ];
641  $data['visitors']['config_quicktime'] = [
642    'title' => t('Quicktime Plugin'),
643    'help' => t("The visitor's browser supports Quicktime."),
644    'field' => [
645      'id' => 'visitors_quicktime',
646    ],
647    'filter' => [
648      'id' => 'boolean',
649    ],
650  ];
651  $data['visitors']['config_realplayer'] = [
652    'title' => t('Realplayer Plugin'),
653    'help' => t("The visitor's browser supports Realplayer."),
654    'field' => [
655      'id' => 'visitors_realplayer',
656    ],
657    'filter' => [
658      'id' => 'boolean',
659    ],
660  ];
661  $data['visitors']['config_windowsmedia'] = [
662    'title' => t('Windows Media Plugin'),
663    'help' => t("The visitor's browser supports Windows Media."),
664    'field' => [
665      'id' => 'visitors_windowsmedia',
666    ],
667    'filter' => [
668      'id' => 'boolean',
669    ],
670  ];
671  $data['visitors']['config_silverlight'] = [
672    'title' => t('Silverlight Plugin'),
673    'help' => t("The visitor's browser supports Silverlight."),
674    'field' => [
675      'id' => 'visitors_silverlight',
676    ],
677    'filter' => [
678      'id' => 'boolean',
679    ],
680  ];
681  $data['visitors']['config_cookie'] = [
682    'title' => t('Cookie Plugin'),
683    'help' => t("The visitor's browser supports cookies."),
684    'field' => [
685      'id' => 'visitors_cookie',
686    ],
687    'filter' => [
688      'id' => 'boolean',
689    ],
690  ];
691  $data['visitors']['config_browser_engine'] = [
692    'title' => t('Browser Engine'),
693    'help' => t('The engine used by the browser.'),
694    'field' => [
695      'id' => 'standard',
696    ],
697    'filter' => [
698      'id' => 'string',
699    ],
700    'argument' => [
701      'id' => 'string',
702    ],
703  ];
704  $data['visitors']['config_browser_name'] = [
705    'title' => t('Browser Name'),
706    'help' => t('The name of the browser.'),
707    'field' => [
708      'id' => 'visitors_browser',
709    ],
710    'filter' => [
711      'id' => 'string',
712    ],
713    'argument' => [
714      'id' => 'string',
715    ],
716  ];
717  $data['visitors']['config_browser_version'] = [
718    'title' => t('Browser Version'),
719    'help' => t('The version of the browser.'),
720    'field' => [
721      'id' => 'standard',
722    ],
723    'filter' => [
724      'id' => 'string',
725    ],
726    'argument' => [
727      'id' => 'string',
728    ],
729  ];
730  $data['visitors']['config_client_type'] = [
731    'title' => t('Client type'),
732    'help' => t('The type of the client.'),
733    'field' => [
734      'id' => 'standard',
735    ],
736    'filter' => [
737      'id' => 'string',
738    ],
739    'argument' => [
740      'id' => 'string',
741    ],
742  ];
743  $data['visitors']['config_device_brand'] = [
744    'title' => t('Device brand'),
745    'help' => t('The brand of the device.'),
746    'field' => [
747      'id' => 'visitors_brand',
748    ],
749    'filter' => [
750      'id' => 'string',
751    ],
752    'argument' => [
753      'id' => 'string',
754    ],
755  ];
756  $data['visitors']['config_device_model'] = [
757    'title' => t('Device model'),
758    'help' => t('The model of the device.'),
759    'field' => [
760      'id' => 'standard',
761    ],
762    'filter' => [
763      'id' => 'string',
764    ],
765    'argument' => [
766      'id' => 'string',
767    ],
768  ];
769  $data['visitors']['config_device_type'] = [
770    'title' => t('Device type'),
771    'help' => t('The type of device.'),
772    'field' => [
773      'id' => 'visitors_device',
774    ],
775    'filter' => [
776      'id' => 'string',
777    ],
778    'argument' => [
779      'id' => 'string',
780    ],
781  ];
782  $data['visitors']['config_os'] = [
783    'title' => t('Operating System'),
784    'help' => t('The operating system.'),
785    'field' => [
786      'id' => 'visitors_operating_system',
787    ],
788    'filter' => [
789      'id' => 'string',
790    ],
791    'argument' => [
792      'id' => 'string',
793    ],
794  ];
795  $data['visitors']['config_os_version'] = [
796    'title' => t('OS version'),
797    'help' => t('The version of the Operating System.'),
798    'field' => [
799      'id' => 'standard',
800    ],
801    'filter' => [
802      'id' => 'string',
803    ],
804    'argument' => [
805      'id' => 'string',
806    ],
807  ];
808  $data['visitors']['bot'] = [
809    'title' => t('Bot'),
810    'help' => t("The visit is from a bot."),
811    'field' => [
812      'id' => 'boolean',
813    ],
814    'filter' => [
815      'id' => 'boolean',
816    ],
817    'argument' => [
818      'id' => 'numeric',
819    ],
820  ];
821  $data['visitors']['language'] = [
822    'title' => t('Language'),
823    'help' => t('The browser language.'),
824    'field' => [
825      'id' => 'visitors_language',
826    ],
827    'filter' => [
828      'id' => 'string',
829    ],
830    'argument' => [
831      'id' => 'string',
832    ],
833  ];
834  $data['visitors']['location_continent'] = [
835    'title' => t('Continent'),
836    'help' => t('The location continent.'),
837    'field' => [
838      'id' => 'visitors_continent',
839    ],
840    'filter' => [
841      'id' => 'string',
842    ],
843    'argument' => [
844      'id' => 'string',
845    ],
846  ];
847  $data['visitors']['location_country'] = [
848    'title' => t('Country'),
849    'help' => t('The location country.'),
850    'field' => [
851      'id' => 'visitors_country',
852    ],
853    'filter' => [
854      'id' => 'string',
855    ],
856    'argument' => [
857      'id' => 'string',
858    ],
859  ];
860
861  $data['visitors']['pf_network'] = [
862    'title' => t('Network'),
863    'help' => t('Network performance.'),
864    'field' => [
865      'id' => 'numeric',
866    ],
867    'sort' => [
868      'id' => 'standard',
869    ],
870    'filter' => [
871      'id' => 'numeric',
872    ],
873    'argument' => [
874      'id' => 'numeric',
875    ],
876  ];
877  $data['visitors']['pf_server'] = [
878    'title' => t('Server'),
879    'help' => t('Server performance.'),
880    'field' => [
881      'id' => 'numeric',
882    ],
883    'sort' => [
884      'id' => 'standard',
885    ],
886    'filter' => [
887      'id' => 'numeric',
888    ],
889    'argument' => [
890      'id' => 'numeric',
891    ],
892  ];
893  $data['visitors']['pf_transfer'] = [
894    'title' => t('Transfer'),
895    'help' => t('Transfer performance.'),
896    'field' => [
897      'id' => 'numeric',
898    ],
899    'sort' => [
900      'id' => 'standard',
901    ],
902    'filter' => [
903      'id' => 'numeric',
904    ],
905    'argument' => [
906      'id' => 'numeric',
907    ],
908  ];
909  $data['visitors']['pf_dom_processing'] = [
910    'title' => t('DOM Processing'),
911    'help' => t('DOM Processing performance.'),
912    'field' => [
913      'id' => 'numeric',
914    ],
915    'sort' => [
916      'id' => 'standard',
917    ],
918    'filter' => [
919      'id' => 'numeric',
920    ],
921    'argument' => [
922      'id' => 'numeric',
923    ],
924  ];
925  $data['visitors']['pf_dom_complete'] = [
926    'title' => t('DOM Complete'),
927    'help' => t('DOM Complete performance.'),
928    'field' => [
929      'id' => 'numeric',
930    ],
931    'sort' => [
932      'id' => 'standard',
933    ],
934    'filter' => [
935      'id' => 'numeric',
936    ],
937    'argument' => [
938      'id' => 'numeric',
939    ],
940  ];
941  $data['visitors']['pf_on_load'] = [
942    'title' => t('On Load'),
943    'help' => t('On Load performance.'),
944    'field' => [
945      'id' => 'numeric',
946    ],
947    'sort' => [
948      'id' => 'standard',
949    ],
950    'filter' => [
951      'id' => 'numeric',
952    ],
953    'argument' => [
954      'id' => 'numeric',
955    ],
956  ];
957  $data['visitors']['pf_total'] = [
958    'title' => t('Total'),
959    'help' => t('Total performance.'),
960    'field' => [
961      'id' => 'numeric',
962    ],
963    'sort' => [
964      'id' => 'standard',
965    ],
966    'filter' => [
967      'id' => 'numeric',
968    ],
969    'argument' => [
970      'id' => 'numeric',
971    ],
972  ];
973
974  $data['visitors']['visitors_display_link'] = [
975    'title' => t('Link to Visitors display'),
976    'help' => t('Displays a link to a non-path-based display of this view while keeping the filter criteria, sort criteria, pager settings and contextual filters.'),
977    'area' => [
978      'id' => 'visitors_display_link',
979    ],
980  ];
981
982  return $data;
983}
984
985/**
986 * Implements hook_token_info().
987 */
988function visitors_token_info() {
989  $entity['total-count'] = [
990    'name' => t("Number of views"),
991    'description' => t("The number of visitors who have read the node."),
992  ];
993  $entity['day-count'] = [
994    'name' => t("Views today"),
995    'description' => t("The number of visitors who have read the node today."),
996  ];
997  $entity['last-view'] = [
998    'name' => t("Last view"),
999    'description' => t("The date on which a visitor last read the node."),
1000    'type' => 'date',
1001  ];
1002
1003  $token = [
1004    'tokens' => [],
1005  ];
1006  $entity_types = \Drupal::config('visitors.config')
1007    ->get('counter.entity_types') ?? [];
1008  foreach ($entity_types as $entity_type) {
1009    $token['tokens'][$entity_type] = $entity;
1010  }
1011
1012  return $token;
1013}
1014
1015/**
1016 * Implements hook_tokens().
1017 */
1018function visitors_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
1019  $token_service = \Drupal::token();
1020  $entity_types = \Drupal::config('visitors.config')
1021    ->get('counter.entity_types') ?? [];
1022  $replacements = [];
1023
1024  if (!in_array($type, $entity_types) || empty($data[$type])) {
1025    return $replacements;
1026  }
1027  $entity = $data[$type];
1028
1029  /** @var \Drupal\visitors\VisitorsCounterInterface $counter_storage */
1030  $counter_storage = \Drupal::service('visitors.counter');
1031
1032  $entity_id = $entity->id();
1033  $entity_view = $counter_storage->fetchView($type, $entity_id);
1034  foreach ($tokens as $name => $original) {
1035    if ($name == 'total-count') {
1036      $replacements[$original] = $entity_view ? $entity_view->getTotalCount() : 0;
1037    }
1038    elseif ($name == 'day-count') {
1039      $replacements[$original] = $entity_view ? $entity_view->getDayCount() : 0;
1040    }
1041    elseif ($name == 'last-view') {
1042      $replacements[$original] = $entity_view ? \Drupal::service('date.formatter')->format($entity_view->getTimestamp()) : t('never');
1043    }
1044  }
1045
1046  if ($created_tokens = $token_service->findWithPrefix($tokens, 'last-view')) {
1047    $replacements += $token_service->generate('date', $created_tokens, ['date' => $entity_view ? $entity_view->getTimestamp() : 0], $options, $bubbleable_metadata);
1048  }
1049
1050  return $replacements;
1051}