etracker-8.x-3.x-dev/etracker.module

etracker.module
<?php

/**
 * @file
 * Drupal Module: eTracker.
 *
 * Adds the required Javascript to all your Drupal pages to allow tracking by
 * the eTracker statistics package.
 */

use Drupal\Component\Serialization\Json;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\etracker\Helper\Constants;

/**
 * Implements hook_help().
 */
function etracker_help(string $route_name, RouteMatchInterface $route_match): string {
  $help = '';
  if ($route_name == 'help.page.etracker') {
    $help = t('<a href=":pk_url">eTracker Analytics</a> is a web analytics software. It gives interesting reports on your website visitors, your popular pages, the search engines keywords they used, the language they speak... and so much more. eTracker aims to be an alternative to Google Analytics.', [':pk_url' => 'https://www.etracker.de/']);
  }

  return $help;
}

/**
 * Implements hook_library_info_build().
 */
function etracker_library_info_build(): array {
  $config = Drupal::config(Constants::ETRACKER_SETTINGS_CONFIG_NAME);
  $data_secure_code = $config->get('account_key');

  $etracker_script_settings = $config->get('etracker_script_settings');
  $libraryName = Constants::ETRACKER_LIBRARY_NAME;
  $libraries = [];
  $libraries[$libraryName] = [
    'js' => [
      Constants::ETRACKER_JS_URL => [
        'type' => 'external',
        'attributes' => [
          'type' => 'text/javascript',
          // This id is required by eTracker:
          'id' => '_etLoader',
          'charset' => 'UTF-8',
          'data-block-cookies' => $etracker_script_settings['data_block_cookies'] ? 'true' : 'false',
          'data-respect-dnt' => $etracker_script_settings['data_respect_dnt'] ? 'true' : 'false',
          'data-secure-code' => $data_secure_code,
          'async' => TRUE,
        ],
      ],
    ],
  ];

  // Add library only if any event tracking is enabled.
  $eventTracking = $config->get('event_tracking');
  $isEventTrackingEnabled =
    $eventTracking['track_mailto']
    || $eventTracking['track_download']
    || $eventTracking['track_external']
    || $eventTracking['track_system_messages'];

  if ($isEventTrackingEnabled) {
    $libraries['event_tracking'] = [
      'js' => [
        'js/etracker.js' => [
          'attributes' => [
            'id' => 'etracker_script',
          ],
        ],
      ],
      'dependencies' => [
        'core/drupalSettings',
        Constants::ETRACKER_FULL_LIBRARY_NAME,
      ],
    ];
  }

  if ($config->get('etracker_scope_script') === Constants::ETRACKER_SCOPE_SCRIPT_HEADER) {
    $libraries[$libraryName] += [
      'header' => TRUE,
    ];
  }

  return $libraries;
}

/**
 * Implements hook_preprocess_HOOK().
 */
function etracker_preprocess_page(array &$variables): void {
  $config = Drupal::config(Constants::ETRACKER_SETTINGS_CONFIG_NAME);

  if (!_etracker_request_should_be_tracked($config)) {
    return;
  }

  // Get the variables for the tracking script.
  $et_variables = _etracker_add_variable($config);

  // Allow other modules to alter the variables.
  Drupal::moduleHandler()->alter('etracker_variables', $et_variables);

  // Get multi-lingual settings.
  $language_settings = _etracker_get_language_settings();

  // Add the language to the page name:
  if (!empty($language_settings['page title suffix'])) {
    $et_variables['et_pagename'] .= ' (' . $language_settings['current language'] . ')';
  }

  // Prepare the et_areas if present.
  if (empty($et_variables['et_areas'])) {
    unset($et_variables['et_areas']);
  }
  else {
    $et_areas = [];
    $et_entry = [$et_variables['et_areas']];
    foreach ($et_entry as $areas) {
      if (!empty($language_settings['area prefix'])) {
        array_unshift($areas, $language_settings['current language']);
      }
      if (!empty($language_settings['area suffix'])) {
        $areas[] = $language_settings['current language'];
      }
      if (is_array($areas)) {
        $et_areas[] = implode('/', $areas);
      }
    }
    if (!empty($et_areas) && count($et_areas) > 1) {
      $et_variables['et_areas'] = implode(',', $et_areas);
    }
    else {
      $et_variables['et_areas'] = $et_entry[0];
    }
  }
}

/**
 * Implements hook_page_attachments_alter().
 */
function etracker_page_attachments_alter(array &$attachments): void {
  $config = Drupal::config(Constants::ETRACKER_SETTINGS_CONFIG_NAME);
  if (!_etracker_request_should_be_tracked($config)) {
    return;
  }

  $eventTracking = $config->get('event_tracking');

  $script_settings = [
    'track_mailto' => $eventTracking['track_mailto'],
    'track_download' => $eventTracking['track_download'],
    'track_download_extensions' => $eventTracking['track_download_extensions'],
    'track_external' => $eventTracking['track_external'],
    'messages' => [],
  ];

  // Prepare messages for tracking.
  if ($message_types = $eventTracking['track_system_messages']) {
    $status_heading = [
      'status' => t('Status message'),
      'warning' => t('Warning message'),
      'error' => t('Error message'),
    ];

    foreach (\Drupal::messenger()->all() as $type => $messages) {
      // Track only the selected message types.
      if (in_array($type, $message_types)) {
        foreach ($messages as $message) {
          $script_settings['messages'][] = [
            'type' => Json::encode($status_heading[$type]),
            'text' => Json::encode(strip_tags($message)),
          ];
        }
      }
    }
  }

  $attachments['#attached']['library'][] = Constants::ETRACKER_FULL_LIBRARY_NAME;
  $attachments['#attached']['library'][] = 'etracker/event_tracking';
  $attachments['#attached']['drupalSettings']['etracker'] = $script_settings;

  $request = Drupal::request();
  $route_match = Drupal::routeMatch();
  $titleObject = Drupal::service('title_resolver')
    ->getTitle($request, $route_match->getRouteObject());

  if ($titleObject instanceof TranslatableMarkup) {
    /** @var Drupal\Core\StringTranslation\TranslatableMarkup $titleObject*/
    $title = $titleObject->getUntranslatedString();
  }
  elseif (is_array($titleObject)) {
    $title = $titleObject['#markup'];
  }
  else {
    $title = $titleObject;
  }

  $title = rawurlencode($title);

  $value = "var et_pagename = \"$title\";";

  $et_variables = _etracker_add_variable($config);

  foreach ($et_variables as $name => $variable) {
    if (is_array($variable)) {
      continue;
    }

    if (is_string($variable)) {
      $value .= sprintf(' var %s = "%s";', $name, rawurlencode($variable));
    }
    elseif (is_bool($variable)) {
      $bool = $variable ? 'true' : 'false';
      $value .= sprintf(' var %s = %s;', $name, $bool);
    }
    else {
      $value .= sprintf(' var %s = %s;', $name, rawurlencode($variable));
    }
  }

  $attachments['#attached']['html_head'][] = [
    [
      '#type' => 'html_tag',
      '#tag' => 'script',
      '#value' => $value,
      '#attributes' => [
        'type' => 'text/javascript',
      ],
    ],
    'etracker-inline1',
  ];
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function etracker_form_user_form_alter(array &$form, FormStateInterface &$form_state): void {
  $config = Drupal::config(Constants::ETRACKER_SETTINGS_CONFIG_NAME);
  /** @var \Drupal\Core\Entity\EntityFormInterface $interface */
  $interface = $form_state->getFormObject();
  $account = $interface->getEntity();

  if (
    Drupal::currentUser()->hasPermission('opt-in or out of etracker tracking')
    && ($custom = $config->get('etracker_track_user')) != Constants::ETRACKER_TRACK_USER_NO_CUSTOMIZATION
  ) {
    $form['etracker'] = [
      '#type' => 'fieldset',
      '#title' => t('etracker configuration'),
      '#weight' => 3,
      '#collapsible' => TRUE,
      '#tree' => TRUE,
    ];

    $description = '';

    switch ($custom) {
      case Constants::ETRACKER_TRACK_USER_TRACKING_ON:
        $description = t('Users are tracked by default, but you are able to opt out.');
        break;

      case Constants::ETRACKER_TRACK_USER_TRACKING_OFF:
        $description = t('Users are <em>not</em> tracked by default, but you are able to opt in.');
        break;
    }

    $current_config = Drupal::service('user.data')
      ->get('etracker', $account->id(), 'etracker_enable_tracking');

    // If the user has already opted in (or out) we keep that choice.
    if (!empty($current_config)) {
      $default_value_etracker_dnt = $current_config['etracker_enable_tracking'];
      // Else our default value depends on the current configuration.
    }
    else {
      $default_value_etracker_dnt = ($config->get('etracker_track_user') === Constants::ETRACKER_TRACK_USER_TRACKING_ON);
    }

    $form['etracker']['etracker_enable_tracking'] = [
      '#type' => 'checkbox',
      '#title' => t('Enable user tracking'),
      '#description' => $description,
      '#default_value' => $default_value_etracker_dnt,
    ];

    $form['actions']['submit']['#submit'][] = 'etracker_form_user_form_submit';
  }
}

/**
 * An etracker form submit callback function.
 *
 * @param mixed $form
 *   The form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state object.
 */
function etracker_form_user_form_submit(&$form, FormStateInterface $form_state) {
  $userData = Drupal::service('user.data');

  $userData->set('etracker', Drupal::currentUser()
    ->id(), 'etracker_enable_tracking', $form_state->getValue('etracker'));
}

/**
 * Helper function to set default variables.
 *
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   A config object.
 *
 * @return array
 *   The default variables.
 */
function _etracker_set_default_variables(ImmutableConfig $config): array {
  $variables = [
    'et_areas' => [],
    '_btNoJquery' => TRUE,
  ];

  // Add breadcrumb as area.
  if ($config->get('etracker_mode_breadcrumb_as_area') != Constants::ETRACKER_MODE_BREADCRUMB_OFF) {
    $breadcrumb_manager = Drupal::service('breadcrumb');
    $current_route_match = Drupal::service('current_route_match');

    /** @var Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb */
    $breadcrumb = $breadcrumb_manager->build($current_route_match);

    foreach ($breadcrumb->getLinks() as $link) {
      if (($config->get('etracker_mode_breadcrumb_as_area') === Constants::ETRACKER_MODE_BREADCRUMB_ON_EXCLUDE_HOME) && ($link->getUrl()
        ->getRouteName() === '<front>')) {
        continue;
      }
      $titles[] = $link->getText();
    }

    if (!empty($titles) && is_array($titles)) {
      // $titles is typically a string, but may also be a render array.
      // We do only handle scalar titles here.
      if (is_scalar($titles[0])) {
        $variables['et_areas'][] = implode('/', $titles);
      }
    }
  }

  // Allow other modules to alter the default variables.
  Drupal::moduleHandler()->alter('etracker_default_variables', $variables);

  return $variables;
}

/**
 * Sets (and stores) the javascript variables required for etracker.
 *
 * (e.g. et_areas)
 *
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   A config object.
 * @param string $key
 *   The key of the variable to set.
 * @param string $value
 *   The value of the variable to set.
 *
 * @return array
 *   The modified stored values.
 */
function _etracker_add_variable(ImmutableConfig $config, ?string $key = NULL, ?string $value = NULL): array {
  $stored_values = &drupal_static(__FUNCTION__);

  if (!isset($stored_values)) {
    $stored_values = _etracker_set_default_variables($config);
  }

  if (isset($key) && isset($value)) {
    if (is_array($stored_values[$key])) {
      if (!in_array($value, $stored_values[$key])) {
        $stored_values[$key][] = $value;
      }
    }
    else {
      $stored_values[$key] = $value;
    }
  }

  return $stored_values;
}

/**
 * Tracking visibility check for pages.
 *
 * Based on visibility setting this function returns TRUE if JS code should
 * be added to the current page and otherwise FALSE.
 *
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   A config object.
 *
 * @return bool
 *   Returns TRUE if JS code should be added to the current page.
 */
function _etracker_path_should_be_tracked(ImmutableConfig $config) {
  $path_should_be_tracked = &drupal_static(__FUNCTION__);

  // Cache tracking result if function is called more than once.
  if (!isset($path_should_be_tracked)) {

    $visible_path_mode = $config->get('etracker_track_path_mode');
    $visible_path_pages = $config->get('etracker_track_paths');

    // Match path if necessary.
    if (!empty($visible_path_pages)) {
      // Convert path to lowercase. This allows comparison of the same path
      // with different case. Ex: /Page, /page, /PAGE.
      $pages = mb_strtolower($visible_path_pages);

      $path = Drupal::service('path.current')->getPath();
      $path_alias = \Drupal::service('path_alias.manager')->getAliasByPath($path);
      if (empty($path_alias)) {
        $path_alias = mb_strtolower($path);
      }
      else {
        $path_alias = mb_strtolower($path_alias);
      }
      $page_match = \Drupal::service('path.matcher')->matchPath($path_alias, $pages) || (($path != $path_alias) && \Drupal::service('path.matcher')->matchPath($path, $pages));
      // When $visible_path_mode has a value of 'all_pages', the tracking
      // code is displayed on all pages except those listed in $pages. When set
      // to 'all_listed', it is displayed only on those pages listed in $pages.
      $track_all_paths = ($visible_path_mode == Constants::ETRACKER_TRACK_PATHS_MODE_ALL);
      $path_should_be_tracked = ($track_all_paths xor $page_match);
    }
    else {
      $path_should_be_tracked = TRUE;
    }
  }
  return $path_should_be_tracked;
}

/**
 * Tracking visibility check for user roles.
 *
 * Based on visibility setting this function returns TRUE if eTracker code
 * should be added for the current role and otherwise FALSE.
 *
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   A config object.
 *
 * @return bool
 *   Returns TRUE if eTracker code should be added for the current role.
 */
function _etracker_role_should_be_tracked(ImmutableConfig $config) {
  $role_should_be_tracked = &drupal_static(__FUNCTION__);

  if (!isset($role_should_be_tracked)) {
    $user = Drupal::currentUser();
    // First we get the user's roles and the ones configured for etracker.
    $user_roles = $user->getRoles();
    $configured_roles = $config->get('etracker_track_roles');

    // Now we can get the roles present in both lists
    // and check if there are any.
    $matching_roles = array_intersect($user_roles, $configured_roles);
    $user_has_any_role = count($matching_roles) > 0;
    $track_all_roles = $config->get('etracker_track_roles_mode') === Constants::ETRACKER_TRACK_ROLES_MODE_ALL;

    // Either the user has a tracked role or no untracked role.
    $role_should_be_tracked = ($user_has_any_role xor $track_all_roles);
  }
  return $role_should_be_tracked;
}

/**
 * Validate the tracking settings for the current user.
 *
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   A config object.
 *
 * @return bool
 *   Return TRUE if the current user should be tracked
 */
function _etracker_user_should_be_tracked(ImmutableConfig $config): bool {
  $user = Drupal::currentUser();

  // Should the current user be excluded from being tracked
  // (based on their roles)?
  if (!_etracker_role_should_be_tracked($config)) {
    return FALSE;
  }

  // If the current user cannot opt out of tracking (either because of
  // configuration or permissions) we should track them:
  if ($config->get('etracker_track_user') === Constants::ETRACKER_TRACK_USER_NO_CUSTOMIZATION || !$user->hasPermission('opt-in or out of etracker tracking')) {
    return TRUE;
  }

  // Now we need to know if the user has:
  // 1.) opted in
  // 2.) opted out
  // 3.) not opted in or out.
  $user_tracking_config = Drupal::service('user.data')
    ->get('etracker', $user->id(), 'etracker_enable_tracking');

  // Has the user has made a choice?
  if (isset($user_tracking_config)) {

    // Has the user chosen to be tracked?
    if ($user_tracking_config['etracker_enable_tracking'] == 1) {
      return TRUE;
    }
    else {
      return FALSE;
    }
    // The user has not made a choice, so we use the configured default.
  }
  else {
    return $config->get('etracker_track_user') === Constants::ETRACKER_TRACK_USER_TRACKING_ON;
  }
}

/**
 * Validate the etracker account key configuration.
 *
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   A config object.
 *
 * @return bool
 *   Whether the account key is set.
 */
function _etracker_account_key_configured(ImmutableConfig $config): bool {
  $account_key = $config->get('account_key');
  if (empty($account_key)) {
    return FALSE;
  }
  else {
    return TRUE;
  }
}

/**
 * Whether the current request should be tracked.
 *
 * @param \Drupal\Core\Config\ImmutableConfig $config
 *   A config object.
 *
 * @return bool
 *   If the request should be tracked.
 */
function _etracker_request_should_be_tracked(ImmutableConfig $config) {
  $request_should_be_tracked = &drupal_static(__FUNCTION__);

  if (!isset($request_should_be_tracked)) {
    $request_should_be_tracked = _etracker_account_key_configured($config) && _etracker_path_should_be_tracked($config) && _etracker_user_should_be_tracked($config);
  }

  return $request_should_be_tracked;
}

/**
 * Helper function for getting the language settings.
 *
 * @return array|mixed|null
 *   Returns an array or mixed or null.
 */
function _etracker_get_language_settings() {
  return [
    'tag' => 0,
    'area prefix' => 0,
    'area suffix' => 0,
    'page title prefix' => 0,
    'current language' => Drupal::languageManager()->getCurrentLanguage()->getId(),
  ];
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc