<?php

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

use Drupal\Component\Serialization\Json;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\google_analytics\Event\GoogleAnalyticsConfigEvent;
use Drupal\google_analytics\Event\GoogleAnalyticsEventsEvent;
use Drupal\google_analytics\GaJavascriptObject;
use Drupal\google_analytics\Constants\GoogleAnalyticsEvents;
use Drupal\google_analytics\Component\Render\GoogleAnalyticsJavaScriptSnippet;

/**
 * Advertise the supported google analytics api details.
 */
function google_analytics_api() {
  return [
    'api' => 'gtag.js',
  ];
}

/**
 * Implements hook_help().
 */
function google_analytics_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.google_analytics':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Google Analytics adds a web statistics tracking system to your website. This system incorporates numerous statistical features. For an extensive listing of these features see the <a href=":project">Google Analytics</a> project site. Beyond that, additional information can be found at the <a href=":documentation">Drupal - Google Analytics documentation</a>.', [':documentation' => 'https://www.drupal.org/node/37694', ':project' => 'https://www.drupal.org/project/google_analytics']) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dt>' . t('Configuring Google Analytics') . '</dt>';
      $output .= '<dd>' . t('All settings for this module can be found on the <a href=":ga_settings">Google Analytics settings</a> page. When entering the Google Analytics account number here, it will automatically add the required JavaScript to every page generated. The <em>General Settings</em> section on this page provides additional instruction about setting up tracking thru the Google account.', [':ga_settings' => Url::fromRoute('google_analytics.admin_settings_form')->toString()]) . '</dd>';
      $output .= '<dt>' . t('Additional features') . '</dt>';
      $output .= '<dd>' . t('The Google Analytics module offers a bit more than basic tracking. <em>Page Tracking</em> for instance allows you to provide a list of pages to track, or a list of pages not to track. Role and Link tracking features are also available. For a comprehensive discussion on the setup and use of its many feature see the <a href=":documentation">Drupal - Google Analytics documentation</a>.', [':documentation' => 'https://www.drupal.org/node/37694']) . '</dd>';
      return $output;

    case 'google_analytics.admin_settings_form':
      return t('<a href=":ga_url">Google Analytics</a> is a free (registration required) website traffic and marketing effectiveness service.', [':ga_url' => 'https://marketingplatform.google.com/about/analytics/']);
  }
}

/**
 * Implements hook_page_attachments().
 *
 * Insert JavaScript to the appropriate scope/region of the page.
 */
function google_analytics_page_attachments(array &$page) {
  $account = \Drupal::currentUser();
  $config = \Drupal::config('google_analytics.settings');
  $request = \Drupal::request();

  /** @var \Drupal\google_analytics\Helpers\VisiblityTracker $visibilityTracker */
  $visibilityTracker = \Drupal::service('google_analytics.visibility');
  /** @var \Drupal\google_analytics\Helpers\GoogleAnalyticsAccounts $ga_accounts */
  $ga_accounts = \Drupal::service('google_analytics.accounts');

  // Add module cache tags.
  $page['#cache']['tags'] = Cache::mergeTags(isset($page['#cache']['tags']) ? $page['#cache']['tags'] : [], $config->getCacheTags());

  // Get page http status code for visibility filtering.
  $status = NULL;
  if ($exception = $request->attributes->get('exception')) {
    $status = $exception->getStatusCode();
  }
  $trackable_status_codes = [
    // "Forbidden" status code.
    '403',
    // "Not Found" status code.
    '404',
  ];

  // 1. Check if the GA account number has a valid value.
  // 2. Track page views based on visibility value.
  // 3. Check if we should track the currently active user's role.
  // 4. Ignore pages visibility filter for 404 or 403 status codes.
  if ($ga_accounts->getDefaultMeasurementId() && ($visibilityTracker->getVisibilityPages() || in_array($status, $trackable_status_codes)) && $visibilityTracker->getUserVisibilty($account)) {
    $default_id = $ga_accounts->getDefaultMeasurementId();
    // Init variables.
    $debug = $config->get('debug');
    $url_custom = '';

    // Instantiate our event.
    $javascript = new GaJavascriptObject($default_id);

    // Add link tracking.
    $link_settings = ['account' => (string) $default_id];
    if ($track_outbound = $config->get('track.outbound')) {
      $link_settings['trackOutbound'] = $track_outbound;
    }
    if ($track_mailto = $config->get('track.mailto')) {
      $link_settings['trackMailto'] = $track_mailto;
    }
    if ($track_tel = $config->get('track.tel')) {
      $link_settings['trackTel'] = $track_tel;
    }
    if (($track_download = $config->get('track.files')) && ($trackfiles_extensions = $config->get('track.files_extensions'))) {
      $link_settings['trackDownload'] = $track_download;
      $link_settings['trackDownloadExtensions'] = $trackfiles_extensions;
    }
    if ((\Drupal::moduleHandler()->moduleExists('colorbox')) && $track_colorbox = $config->get('track.colorbox')) {
      $link_settings['trackColorbox'] = $track_colorbox;
    }
    if ($track_domain_mode = $config->get('domain_mode')) {
      $link_settings['trackDomainMode'] = $track_domain_mode;
    }
    if ($track_cross_domains = $config->get('cross_domains')) {
      $link_settings['trackCrossDomains'] = preg_split('/(\r\n?|\n)/', $track_cross_domains);
    }
    if ($track_url_fragments = $config->get('track.urlfragments')) {
      $link_settings['trackUrlFragments'] = $track_url_fragments;
    }

    $page['#attached']['drupalSettings']['google_analytics'] = $link_settings;

    // Add debugging code.
    if ($debug) {
      $page['#attached']['library'][] = 'google_analytics/google_analytics.debug';
      // phpcs:disable
      // Add the JS test in development to the page.
      // $page['#attached']['library'][] = 'google_analytics/google_analytics.test';
      // phpcs:enable
    }
    else {
      $page['#attached']['library'][] = 'google_analytics/google_analytics';
    }

    if ($config->get('track.adsense')) {
      // Custom tracking. Prepend before all other JavaScript.
      // @TODO: https://support.google.com/adsense/answer/98142
      // sounds like it could be appended to $script.
      $script = $javascript->getAdsenseScript();
    }

    // Build tracker code.
    $script = 'window.dataLayer = window.dataLayer || [];';
    $script .= 'function gtag(){dataLayer.push(arguments)};';
    $script .= 'gtag("js", new Date());';
    $script .= 'gtag("set", "developer_id.dMDhkMT", true);';

    // Add any custom code snippets if specified.
    $codesnippet_before = $config->get('codesnippet.before');
    $codesnippet_after = $config->get('codesnippet.after');

    // Create a tracker.
    if (!empty($codesnippet_before)) {
      $script .= $codesnippet_before;
    }

    // Create a config for each account.
    foreach($ga_accounts->getAccounts() as $account) {
      $ga_config = new GoogleAnalyticsConfigEvent($javascript, $account);

      // Get the event_dispatcher service and dispatch the event.
      $event_dispatcher = \Drupal::service('event_dispatcher');
      $event_dispatcher->dispatch($ga_config, GoogleAnalyticsEvents::ADD_CONFIG);

      // Json::encode() cannot convert custom URLs properly.
      $config_array = $ga_config->getConfig();
      $path = '';
      $path_type = '';
      if (isset($config_array['page'])) {
        $path_type = substr($config_array['page_placeholder'], strlen('PLACEHOLDER_'));
        $path = $config_array['page'];
        $config_array[$path_type] = $config_array['page_placeholder'];
        unset($config_array['page']);
        unset($config_array['page_placeholder']);
      }
      $arguments_json = Json::encode($config_array);
      $arguments_json = str_replace('"PLACEHOLDER_'.$path_type.'"', $path, $arguments_json);


      $script .= 'gtag("config", ' . Json::encode((string)$account) . ', ' . $arguments_json . ');';
    }


    $ga_events = new GoogleAnalyticsEventsEvent($javascript);

    // Get the event_dispatcher service and dispatch the event.
    $event_dispatcher = \Drupal::service('event_dispatcher');
    $event_dispatcher->dispatch($ga_events, GoogleAnalyticsEvents::ADD_EVENT);

    if (!empty($ga_events->getEvents())) {
      foreach ($ga_events->getEvents() as $event) {
        $event_name = array_key_first($event);
        $event_parameters = $event[$event_name];
        $script .= 'gtag("event", ' . Json::encode($event_name) . ', ' . Json::encode($event_parameters) . ');';
      }
    }
    if (!empty($codesnippet_after)) {
      $script .= $codesnippet_after;
    }

    // Prepend tracking library directly before script code.
    $javascript_service = \Drupal::service('google_analytics.javascript_cache');

    $page['#attached']['html_head'][] = [
      [
        '#tag' => 'script',
        '#attributes' => [
          'async' => TRUE,
          'src' => $javascript_service->fetchGoogleAnalyticsJavascript($default_id),
        ],
      ],
      'google_analytics_tracking_file',
    ];

    $page['#attached']['html_head'][] = [
      [
        '#tag' => 'script',
        '#value' => new GoogleAnalyticsJavaScriptSnippet($script),
      ],
      'google_analytics_tracking_script',
    ];
  }
}

/**
 * Implements hook_entity_extra_field_info().
 */
function google_analytics_entity_extra_field_info() {
  $extra['user']['user']['form']['google_analytics'] = [
    'label' => t('Google Analytics settings'),
    'description' => t('Google Analytics module form element.'),
    'weight' => 3,
  ];

  return $extra;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function google_analytics_form_user_form_alter(&$form, FormStateInterface $form_state) {
  $config = \Drupal::config('google_analytics.settings');
  /** @var Drupal\user\ProfileForm $profileForm */
  $profileForm = $form_state->getFormObject();
  /** @var Drupal\user\Entity\User $account */
  $account = $profileForm->getEntity();
  /** @var \Drupal\google_analytics\Helpers\VisiblityTracker $visibilityTracker */
  $visibilityTracker = \Drupal::service('google_analytics.visibility');

  if ($account->hasPermission('opt-in or out of google analytics tracking') && ($visibility_user_account_mode = $config->get('visibility.user_account_mode')) != 0 && $visibilityTracker->getVisibilityRoles($account)) {
    $account_data_google_analytics = \Drupal::service('user.data')->get('google_analytics', $account->id());

    $form['google_analytics'] = [
      '#type' => 'details',
      '#title' => t('Google Analytics settings'),
      '#weight' => 3,
      '#open' => TRUE,
    ];

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

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

    $form['google_analytics']['user_account_users'] = [
      '#type' => 'checkbox',
      '#title' => t('Enable user tracking'),
      '#description' => $description,
      '#default_value' => isset($account_data_google_analytics['user_account_users']) ? $account_data_google_analytics['user_account_users'] : ($visibility_user_account_mode == 1),
    ];

    // hook_user_update() is missing in D8, add custom submit handler.
    $form['actions']['submit']['#submit'][] = 'google_analytics_user_profile_form_submit';
  }
}

/**
 * Submit callback for user profile form to save the Google Analytics setting.
 */
function google_analytics_user_profile_form_submit($form, FormStateInterface $form_state) {
  /** @var Drupal\user\ProfileForm $profileForm */
  $profileForm = $form_state->getFormObject();
  /** @var Drupal\user\Entity\User $account */
  $account = $profileForm->getEntity();
  if ($account->id() && $form_state->hasValue('user_account_users')) {
    \Drupal::service('user.data')->set('google_analytics', $account->id(), 'user_account_users', (int) $form_state->getValue('user_account_users'));
  }
}

/**
 * Implements hook_cron().
 */
function google_analytics_cron() {
  $config = \Drupal::config('google_analytics.settings');
  $request_time = \Drupal::time()->getRequestTime();
  $javascript_service = \Drupal::service('google_analytics.javascript_cache');
  $ga_accounts = \Drupal::service('google_analytics.accounts');

  // Return prematurely if no default measurement ID was found.
  if (empty($ga_accounts->getDefaultMeasurementId())) {
    return;
  }

  // Regenerate the tracking code file every day.
  if ($request_time - \Drupal::state()->get('google_analytics.last_cache') >= 86400 && $config->get('cache')) {
    $javascript_service->fetchGoogleAnalyticsJavascript($ga_accounts->getDefaultMeasurementId(), TRUE);
    \Drupal::state()->set('google_analytics.last_cache', $request_time);
  }
}

/**
 * Implements hook_preprocess_item_list__search_results().
 *
 * Collects and adds the number of search results to the head.
 */
function google_analytics_preprocess_item_list__search_results(&$variables) {
  $config = \Drupal::config('google_analytics.settings');

  // Only run on search results list.
  if ($config->get('track.site_search')) {
    // Get the pager manager to give us the number of items returned.
    /** @var \Drupal\Core\Pager\PagerManagerInterface $pager_manager */
    $pager_manager = \Drupal::service('pager.manager');
    $items = 0;
    if ($pager_manager->getPager()) {
      $items = $pager_manager->getPager()->getTotalItems();
    }
    $variables['#attached']['html_head'][] = [
      [
        '#tag' => 'script',
        '#value' => 'window.google_analytics_search_results = ' . $items . ';',
        '#weight' => JS_LIBRARY - 1,
      ],
      'google_analytics_search_script',
    ];
  }
}


