diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cde8069 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.php diff --git a/README.md b/README.md index 6bbd71e..f820263 100755 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ ## GDX-Analytics-Drupal-Snowplow The GDX Analytics Drupal Snowplow module installs and runs Snowplow - Javascript web trackers, and provides an interface to configure them. + Javascript web trackers and provides an interface to configure them. ## Requirements - This module requires Drupal 10. Backwards compatibilty tests are also conducted for Drupal 9 & 8. + This module requires Drupal 10. Backwards compatibility tests are also conducted for Drupal 9. ## Project Status This project is currently under development and actively supported by the GDX Analytics Team. -This 3.0 version reverts to using Drupal standard search. If you are currently using the 2.0 version with Search API, do not upgrade to 3.0. A future version of the 3.x will resolve this issue. +This version (3.1.0) now allows the use of GDX Analytics Drupal Snowplow module with other search modules, including Drupal Search API. ## Relevant Repositories [GDX-Analytics/](https://github.com/bcgov/GDX-Analytics/) @@ -21,21 +21,19 @@ This is the central repository for work by the GDX Analytics Team. ## Installation - Install as you would normally install a contributed Drupal module. Visit: - https://www.drupal.org/docs/extending-drupal/installing-modules - for further information. +Install as you would normally install a contributed Drupal module. Visit: https://www.drupal.org/docs/extending-drupal/installing-modules for further information. - In your drupal installation, change directories to your sites/modules/custom folder. +Change directories to your sites/modules/custom folder in your drupal installation. - Clone the project from github: https://github.com/bcgov/GDX-Analytics-Drupal-Snowplow. +Clone the project from github: https://github.com/bcgov/GDX-Analytics-Drupal-Snowplow. - Install the module in Administration » Extend. Select the module; then scroll down and click Install. +Install the module in Administration » Extend. Select the modul, then scroll down and click Install. ## Configuration - Configure settings in Administration » Configuration » GDX Analytics Drupal Snowplow settings. +Configure settings in Administration » Configuration » GDX Analytics Drupal Snowplow settings. - Use this Configuration page to set the collector environment, tracking script URI, and app ID. +Use this Configuration page to set the collector environment, tracking script URI, app ID and search path. ## Getting Help or Reporting an Issue @@ -47,7 +45,7 @@ The GDX Analytics Team are the main contributors to this project and maintain th ## How to Contribute -If you would like to contribute, please see our [CONTRIBUTING](CONTRIBUTING.md) guideleines. +If you would like to contribute, please see our [CONTRIBUTING](CONTRIBUTING.md) guidelines. Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. diff --git a/config/install/gdx_analytics_drupal_snowplow.settings.yml b/config/install/gdx_analytics_drupal_snowplow.settings.yml index 72182b9..5456df9 100755 --- a/config/install/gdx_analytics_drupal_snowplow.settings.yml +++ b/config/install/gdx_analytics_drupal_snowplow.settings.yml @@ -1,4 +1,5 @@ gdx_collector_mode: gdx_analytics_snowplow_version: gdx_analytics_snowplow_script_uri: -gdx_analytics_app_id: \ No newline at end of file +gdx_analytics_app_id: +gdx_analytics_search_path: /search # default search path for standard search \ No newline at end of file diff --git a/gdx_analytics_drupal_snowplow.info.yml b/gdx_analytics_drupal_snowplow.info.yml index 48718dc..23c3aac 100755 --- a/gdx_analytics_drupal_snowplow.info.yml +++ b/gdx_analytics_drupal_snowplow.info.yml @@ -4,4 +4,4 @@ description: 'Configures the Javascript tracker for the BC Government GDX Analyt core: 8.x core_version_requirement: ^8 || ^9 || ^10 package: 'Analytics' -version: '3.0.0' \ No newline at end of file +version: '3.1.0' \ No newline at end of file diff --git a/gdx_analytics_drupal_snowplow.libraries.yml b/gdx_analytics_drupal_snowplow.libraries.yml index b14b384..aed35ad 100755 --- a/gdx_analytics_drupal_snowplow.libraries.yml +++ b/gdx_analytics_drupal_snowplow.libraries.yml @@ -1,5 +1,5 @@ gdx_analytics_drupal_snowplow.webtracker: - version: 3.0.0 + version: 3.1.0 header: true js: js/SnowplowInlineCode.js: {} @@ -7,7 +7,7 @@ gdx_analytics_drupal_snowplow.webtracker: - core/drupalSettings gdx_analytics_drupal_snowplow.webtracker_search: - version: 3.0.0 + version: 3.1.0 header: true js: js/SnowplowInlineCodeSearch.js: {} diff --git a/gdx_analytics_drupal_snowplow.module b/gdx_analytics_drupal_snowplow.module index 9c7ed5b..315ef0e 100644 --- a/gdx_analytics_drupal_snowplow.module +++ b/gdx_analytics_drupal_snowplow.module @@ -5,23 +5,37 @@ * Contains gdx_analytics_drupal_snowplow.module. */ -use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Link; use Drupal\Core\Url; +// Define constants for route names. +define('GDX_ANALYTICS_SETTINGS_FORM_ROUTE', 'gdx_analytics_drupal_snowplow.gdx_analytics_drupal_snowplow_settings_form'); +define('GDX_ANALYTICS_HELP_PAGE_ROUTE', 'help.page.gdx_analytics_drupal_snowplow'); + +// Define constant for tracker versions. +define('GDX_ANALYTICS_WEBTRACKER', 'gdx_analytics_drupal_snowplow/gdx_analytics_drupal_snowplow.webtracker'); +define('GDX_ANALYTICS_WEBTRACKER_WITH_SEARCH', 'gdx_analytics_drupal_snowplow/gdx_analytics_drupal_snowplow.webtracker_search'); + +// Define constant for configuration name. +define('GDX_ANALYTICS_CONFIG_NAME', 'gdx_analytics_drupal_snowplow.settings'); + +// Define constant for warning message about incomplete configuration. +define('GDX_ANALYTICS_WARNING_CONFIG', t('Please Configure Your GDX Analytics Drupal Snowplow Module.
For assistance with filling out this form, contact the GDX Analytics Team.')); + +// Define constant for warning message about configuring the module. +define('GDX_ANALYTICS_WARNING_CONFIG_LINK', 'Please click here to configure your GDX Analytics Drupal Snowplow module.'); + +// Define constant for the module description. +define('GDX_ANALYTICS_MODULE_DESCRIPTION', t('This is the GDX Analytics Drupal Snowplow module.')); + + /** * Implements hook_help(). */ -function gdx_analytics_drupal_snowplow_help($route_name, RouteMatchInterface $route_match) { - switch ($route_name) { - // Main module help for the gdx_analytics_drupal_snowplow module. - case 'help.page.gdx_analytics_drupal_snowplow': - $output = ''; - $output .= '

' . t('About') . '

'; - $output .= '

' . t('This is the GDX Analytics Drupal Snowplow module.') . '

'; - return $output; - - default: +function gdx_analytics_drupal_snowplow_help($route_name) { + // Help text for the module. $route_name is as part of Drupal routing system + if ($route_name === GDX_ANALYTICS_HELP_PAGE_ROUTE) { + return '

' . t('About') . '

' . GDX_ANALYTICS_MODULE_DESCRIPTION . '

'; } } @@ -29,56 +43,110 @@ function gdx_analytics_drupal_snowplow_help($route_name, RouteMatchInterface $ro * Implements hook_page_attachments(). */ function gdx_analytics_drupal_snowplow_page_attachments(array &$page) { - //config file for default setting - current empty - gets updated when form is used - $config = \Drupal::config('gdx_analytics_drupal_snowplow.settings'); - //we set collector, script uri, app_id, and snowplow_version from the config file - $collector = $config->getRawData()['gdx_collector_mode']; - $script_uri = $config->getRawData()['gdx_analytics_snowplow_script_uri']; - $app_id = $config->getRawData()['gdx_analytics_app_id']; - $snowplow_version = $config->getRawData()['gdx_analytics_snowplow_version']; - - $page['#attached']['drupalSettings']['gdx_collector'] = $collector; - $page['#attached']['drupalSettings']['app_id'] = $app_id; - $page['#attached']['drupalSettings']['snowplow_version'] = $snowplow_version; + // Initialize variables. $admin_context = \Drupal::service('router.admin_context'); + $messenger = \Drupal::messenger(); + $logger = \Drupal::logger('gdx_analytics_drupal_snowplow'); - // Get route info and set up link to settings form. - // This warning shows after module has been installed but no configurations are set - $curr_route_name = \Drupal::routeMatch()->getRouteName(); - $form_route = Url::fromRoute('gdx_analytics_drupal_snowplow.gdx_analytics_drupal_snowplow_settings_form'); - $link = Link::fromTextAndUrl('Please configure your GDX Analytics Drupal Snowplow module', $form_route); + // Check if the current route is an administrative route. + if ($admin_context->isAdminRoute()) { + // Handle administrative routes. + handleAdminRoutes($logger, $messenger); + } else { + // Handle non-administrative routes. + handleNonAdminRoutes($page, $logger); + } +} + +/** + * Handle logic for administrative routes. + */ +function handleAdminRoutes($logger, $messenger) { + // Create link for configuration. + $link = Link::fromTextAndUrl(GDX_ANALYTICS_WARNING_CONFIG_LINK, Url::fromRoute(GDX_ANALYTICS_SETTINGS_FORM_ROUTE)); - // Look for standard search default route and enable search event. - if($curr_route_name == 'search.view_node_search') { - $page['#attached']['drupalSettings']['search'] = true; + // Check if configuration is incomplete and display appropriate message. + if (isConfigurationIncomplete($logger, $messenger)) { + if ($route_name === GDX_ANALYTICS_SETTINGS_FORM_ROUTE) { + $messenger->addWarning(GDX_ANALYTICS_WARNING_CONFIG); + } else { + $messenger->addWarning($link); + } } +} - $messenger = \Drupal::messenger(); +/** + * Handle logic for non-administrative routes. + */ +function handleNonAdminRoutes(&$page, $logger) { + try { - // Set admin message if settings form not complete. - if($admin_context->isAdminRoute()){ - // Check that the Module has been configured; if there are fields missing, set a warning. - if(empty($collector) || empty($script_uri) || empty($app_id)){ - if($curr_route_name == 'gdx_analytics_drupal_snowplow.gdx_analytics_drupal_snowplow_settings_form') { - $messenger->addWarning(t('Please Configure Your GDX Analytics Drupal Snowplow Module.
' - . 'For assistance with filling out this form, contact the GDX Analytics Team.')); - }else { - $messenger->addWarning($link); - } + // Get configuration settings. + $settings = \Drupal::config(GDX_ANALYTICS_CONFIG_NAME)->getRawData(); + + // Set collector, script uri, app_id, snowplow_version and search path. + $page['#attached']['drupalSettings'] = [ + 'gdx_collector' => $settings['gdx_collector_mode'], + 'app_id' => $settings['gdx_analytics_app_id'], + 'snowplow_version' => $settings['gdx_analytics_snowplow_version'], + 'search_path' => $settings['gdx_analytics_search_path'], + ]; + + // Attach the main tracking code url. + $page['#attached']['drupalSettings']['script_uri'] = $settings['gdx_analytics_snowplow_script_uri']; + + // Handle version-specific logic. + if ($settings['gdx_analytics_snowplow_version'] == 1) { + // Handle Web tracker WITH search function + handleTrackerWithSearch($page, $settings); + } elseif ($settings['gdx_analytics_snowplow_version'] == 0) { + // Handle Web tracker WITHOUT search function + $page['#attached']['library'][] = GDX_ANALYTICS_WEBTRACKER; } + + } catch (\Exception $e) { + // Log the exception and display a message + $logger->error('Error processing non-admin routes: @message', ['@message' => $e->getMessage()]); } +} - // Attach the tracking code to front-end pages based on the configuration - if(!$admin_context->isAdminRoute()){ - $page['#attached']['drupalSettings']['script_uri'] = $script_uri; - if($snowplow_version == 1) { - $std_key = Drupal::request()->query->get('keys'); - if(!empty($std_key)) { - $page['#attached']['drupalSettings']['search_terms'] = explode(' ', $std_key); - } - $page['#attached']['library'][] = 'gdx_analytics_drupal_snowplow/gdx_analytics_drupal_snowplow.webtracker_search'; - } elseif ($snowplow_version == 0) { - $page['#attached']['library'][] = 'gdx_analytics_drupal_snowplow/gdx_analytics_drupal_snowplow.webtracker'; +/** + * Handle Web tracker WITH search function + */ +function handleTrackerWithSearch(&$page, $settings) { + // Check if the URI contains the search path (default path is '/search' for Drupal Standard Search). + $curr_uri = \Drupal::request()->getRequestUri(); + + if (strpos($curr_uri, $settings['gdx_analytics_search_path']) !== false) { + // Retrieve the search query parameters (first value of an array) key value pairs passed to the current script via the URL parameters. + $search_terms = reset($_GET); + if (!empty($search_terms)) { + $page['#attached']['drupalSettings']['search'] = true; + // Assign the array of individual search terms (spaces as delimiters). + $page['#attached']['drupalSettings']['search_terms'] = explode(' ', $search_terms); } } + // Attach the tracking code to front-end pages if search toggle is enabled. + $page['#attached']['library'][] = GDX_ANALYTICS_WEBTRACKER_WITH_SEARCH; } + +/** + * Check if configuration settings are incomplete. + */ +function isConfigurationIncomplete($logger, $messenger) { + try { + + // Get configuration settings. + $config = \Drupal::config(GDX_ANALYTICS_CONFIG_NAME); + // Retrieve raw data. + $settings = $config->getRawData(); + + // Check if any setting is empty. + return in_array('', $settings); + } catch (\Exception $e) { + // Log the exception and display a message + $logger->error('Error retrieving configuration: @message', ['@message' => $e->getMessage()]); + $messenger->addError(t('An error occurred while processing the configuration.')); + return false; + } +} \ No newline at end of file diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index f082dbc..b81e494 100755 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -34,7 +34,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['gdx_collector_mode'] = [ '#type' => 'textfield', - '#title' => $this->t('Enter Collector Environment'), + '#title' => $this->t('Collector Environment'), '#default_value' => $config->get('gdx_collector_mode'), '#description' => $this->t('Enter the value for the Snowplow endpoint as provided to you. Do not include "https://" or "http://"'), '#maxlength' => 128, @@ -43,7 +43,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['gdx_analytics_snowplow_script_uri'] = [ '#type' => 'textfield', - '#title' => $this->t('Enter Snowplow tracking script URI'), + '#title' => $this->t('Snowplow tracking script URI'), '#default_value' => $config->get('gdx_analytics_snowplow_script_uri'), '#description' => $this->t('Enter the URL of the Snowplow Library as provided to you. This should be a full URL including "https://" or "http://"'), '#maxlength' => 256, @@ -52,18 +52,27 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['gdx_analytics_app_id'] = [ '#type' => 'textfield', - '#title' => $this->t('Enter App ID'), + '#title' => $this->t('App ID'), '#default_value' => $config->get('gdx_analytics_app_id'), '#description' => $this->t('Enter the value of the App ID for your site as provided to you.'), '#maxlength' => 256, '#size' => 60, '#required' => true, ]; + $form['gdx_analytics_search_path'] = [ + '#type' => 'textfield', + '#title' => $this->t('Search Path'), + '#default_value' => $config->get('gdx_analytics_search_path'), + '#description' => $this->t('If you are using a search module other than Standard Search, change this search path to the path you require.'), + '#maxlength' => 256, + '#size' => 60, + '#required' => true, + ]; $form['gdx_analytics_snowplow_version'] = [ '#type' => 'checkbox', - '#title' => $this->t('Snowplow Search on/off'), + '#title' => $this->t('Snowplow Search Event'), '#default_value' => $config->get('gdx_analytics_snowplow_version'), - '#description' => $this->t('If checked, Snowplow will track searches made using the Drupal search module.'), + '#description' => $this->t('If checked, Snowplow will track the searches performed on the website.'), '#size' => 60, ]; return parent::buildForm($form, $form_state); @@ -74,6 +83,25 @@ public function buildForm(array $form, FormStateInterface $form_state) { */ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); + + // Validate the search path to ensure it starts with '/'. + $search_path = $form_state->getValue('gdx_analytics_search_path'); + if (!empty($search_path) && substr($search_path, 0, 1) !== '/') { + $form_state->setErrorByName('gdx_analytics_search_path', $this->t('The Search Route must start with "/".')); + } + + // Validate the Snowplow tracking script URI to ensure it's a complete URL with 'http://' or 'https://'. + $script_uri = $form_state->getValue('gdx_analytics_snowplow_script_uri'); + if (!empty($script_uri) && !filter_var($script_uri, FILTER_VALIDATE_URL) && substr($script_uri, 0, 1) !== 'http') { + $form_state->setErrorByName('gdx_analytics_snowplow_script_uri', $this->t('The Snowplow tracking script URI must be a complete URL starting with "http://" or "https://".')); + } + + // Validate the collector mode to ensure it doesn't start with 'http://' or 'https://'. + $collector_mode = $form_state->getValue('gdx_collector_mode'); + if (!empty($collector_mode) && (strpos($collector_mode, 'http://') === 0 || strpos($collector_mode, 'https://') === 0)) { + $form_state->setErrorByName('gdx_collector_mode', $this->t('The Collector Environment should not include "http://" or "https://".')); + } + } /** @@ -82,12 +110,22 @@ public function validateForm(array &$form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); - $this->config('gdx_analytics_drupal_snowplow.settings') - ->set('gdx_collector_mode', $form_state->getValue('gdx_collector_mode')) - ->set('gdx_analytics_snowplow_version', $form_state->getValue('gdx_analytics_snowplow_version')) - ->set('gdx_analytics_snowplow_script_uri', $form_state->getValue('gdx_analytics_snowplow_script_uri')) - ->set('gdx_analytics_app_id', $form_state->getValue('gdx_analytics_app_id')) - ->save(); - } + try { + // Save the form values to configuration. + $this->config('gdx_analytics_drupal_snowplow.settings') + ->set('gdx_collector_mode', $form_state->getValue('gdx_collector_mode')) + ->set('gdx_analytics_snowplow_version', $form_state->getValue('gdx_analytics_snowplow_version')) + ->set('gdx_analytics_snowplow_script_uri', $form_state->getValue('gdx_analytics_snowplow_script_uri')) + ->set('gdx_analytics_app_id', $form_state->getValue('gdx_analytics_app_id')) + ->set('gdx_analytics_search_path', $form_state->getValue('gdx_analytics_search_path')) + ->save(); + + // Drupal will provide "The configuration options have been saved." message + } catch (\Exception $e) { + // Log the exception and display a message + \Drupal::logger('gdx_analytics_drupal_snowplow')->error('An error occurred while saving settings: @message', ['@message' => $e->getMessage()]); + $this->messenger()->addError($this->t('An error occurred while saving settings.')); + } + } }