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