You are here

optimizely.module in Optimizely 8.3

Optimizely module.

Originally for Visual Website Optimizer by Awesome Software, http://www.awesome-software.net/ and Ted Cooper (ELC) http://drupal.org/user/784944

Ported to Optimizely by netstudio.gr and Yannis Karampelas (http://drupal.org/user/1145950).

Adds Optimizely javascript (snippet) to the page which loads the A/B test from the optimizely.com website.

7.x-2.x Functionality added to support multiple project entries from an Optimizely account. Each entry specifies target paths to include the Optimizely projects on the specific site paths. Targeting paths eliminates the need to load Optimizely tests on every page of a site.

7.x-2.x by Darren "Dee" Lee (DeeZone: http://drupal.org/user/288060) and Peter Lehrer (plehrer: http://drupal.org/user/2257350) Sponsored by DoSomething.org (http://www.dosomething.org)

8.x-dev by Earl Fong (tz_earl: https://www.drupal.org/user/2531114/)

File

optimizely.module
View source
<?php

/**
 * @file
 * Optimizely module.
 *
 * Originally for Visual Website Optimizer by
 * Awesome Software, http://www.awesome-software.net/ and
 * Ted Cooper (ELC) http://drupal.org/user/784944
 *
 * Ported to Optimizely by netstudio.gr and Yannis Karampelas
 * (http://drupal.org/user/1145950).
 *
 * Adds Optimizely javascript (snippet) to the page which loads the A/B test
 * from the optimizely.com website.
 *
 * 7.x-2.x Functionality added to support multiple project entries from an
 * Optimizely account. Each entry specifies target paths to include the
 * Optimizely projects on the specific site paths. Targeting paths
 * eliminates the need to load Optimizely tests on every page of a site.
 *
 * 7.x-2.x by Darren "Dee" Lee (DeeZone: http://drupal.org/user/288060)
 * and Peter Lehrer (plehrer: http://drupal.org/user/2257350)
 * Sponsored by DoSomething.org (http://www.dosomething.org)
 *
 * 8.x-dev by Earl Fong (tz_earl: https://www.drupal.org/user/2531114/)
 */
use Drupal\Core\Url;

/**
 * Implements hook_help().
 *
 * Help text related to the module's functionality and use.
 */
function optimizely_help($route_name, $arg) {
  switch ($route_name) {
    case 'help.page.optimizely':
      return t('<p><a href="http://optimize.ly/OZRdc0">Optimizely</a> is a third
         party service to add A/B testing to a web site. The tests are applied to
         the site by loading javascript snippets generated by the Optimizely web
         site. The generated javascript files are applied to certain paths on the
         site based on Project entries managed by the Optimizely module. To start
         to apply the general, sitewide Optimizely javascript file the
         <a href="@settings">Optimizely account ID</a> must be entered in the
         module administration page.</p>
         <p>Enable or disable each project entry to apply the project settings
         while not needing to remove the actual entry. The default entry can be
         disabled when additional project entries are made with more specific
         settings. This can include using the orginal Project Code.</p>', [
        '@settings' => Url::fromRoute('optimizely.settings')
          ->toString(),
      ]);
    case 'optimizely.listing':
      return t('<p>A listing of the Optimizely projects. Each entry can be
         enabled / disabled for specific or wildcard paths. Enabled entries are
         highlighted in green while disabled entries are in red. The top,
         "Default" entry cannot be deleted but its settings can be adjusted or
         completely disabled.</p>
         <p>The goal of having multiple projects is to minimize the size of the
         optimizely hosted javascript file. If all experiments are contained in a
         single file and processed on every page load there may be an issue with
         increased page load time. Having multiple projects and loading them on
         specific paths that apply to the experiments helps to minimize the size
         of the file and eliminate processing unused javascript on the user\'s
         browser.</p>');
    case 'optimizely.add_update':
    case 'optimizely.add_update.oid':
      return t('Add or edit specific project entries. Each entry should have an
         Optimizely project / experiment assigned to it, as well as a range of
         website paths where the Optimizely javascript hosted file should be
         included.');
    case 'optimizely.settings':
      return t('Add the Optimizely account ID supplied by the Optimizely website.
         The account ID is essential to setting up the initial sitewide default
         project entry.');
  }
}

/**
 * Implements hook_theme().
 *
 * All of the template definitions. All related templates can be found in
 * /template in the module folder. The template layout can be overridden
 * by the site theme by copying these template files into theme directory.
 */
function optimizely_theme($existing, $type, $theme, $path) {
  return [
    'optimizely_account_settings_form' => [
      'render element' => 'form',
      'template' => 'optimizely-account-settings-form',
    ],
    'optimizely_add_update_form' => [
      'render element' => 'form',
      'template' => 'optimizely-add-update-form',
    ],
  ];
}

/**
 * Implements hook_page_attachments().
 *
 * Checks each page that is about to be rendered as to
 * whether to insert a snippet of Optimizely code or not.
 */
function optimizely_page_attachments(array &$page) {
  $url = Url::fromRoute('<current>');
  $current_path = $url
    ->getInternalPath();
  $current_path = _check_path($current_path);
  $current_path_alias = _lookup_path_alias($current_path);
  $add_snippet = FALSE;

  // Load all entries in the optimizely table.
  $query = \Drupal::database()
    ->select('optimizely', 'o', [
    'target' => 'slave',
  ])
    ->fields('o')
    ->condition('o.enabled', 1, '=')
    ->orderBy('oid');

  // Fetch the result set.
  $result = $query
    ->execute();
  if (!$result) {
    return;
  }

  // Query results found.
  $cache_tag = NULL;

  // Loop through each row of the found results.
  while ($project = $result
    ->fetchAssoc()) {

    // Only process the entries that are enabled.
    if (!$project['enabled']) {
      continue;
    }

    // Target paths from database for project.
    $project_paths = unserialize($project['path']);

    // Remember to check all paths for alias or sytem URL values.
    foreach ($project_paths as $proj_path) {

      // If the Optimizely snippet is added, use the project's path as one
      // of the cache tags for this page. Cache invalidation is triggered
      // via cache tags when project paths are modified or deleted. Spaces
      // are not allowed in cache_tags, but all other characters seem to be
      // allowed, including *. See class CacheRefresher which does the
      // qinvalidating.
      $cache_tag = 'optimizely:' . $proj_path;

      // Remove parameters, if any.
      if (strpos($proj_path, '?') !== FALSE) {
        $proj_path = substr($proj_path, 0, strpos($proj_path, '?'));
      }

      // Look for sitewide wild card.
      if ($proj_path == '*') {
        $add_snippet = TRUE;
        break 2;
      }

      // Look for wildcard match that is not sitewide.
      if (strpos($proj_path, '*') !== FALSE) {

        // Remove wildcard, get base path(s)
        $proj_path = substr($proj_path, 0, -2);

        // Look for wildcard match.
        if (stripos($current_path, $proj_path) === 0 || stripos(_lookup_path_alias($current_path), $proj_path) === 0 || stripos(_lookup_system_path($current_path), $proj_path) === 0) {
          $add_snippet = TRUE;
          break 2;
        }
      }

      // Build a string containing as many as three possible variants
      // of $proj_path.
      $paths_to_check = $proj_path;
      $proj_path_source = _lookup_system_path($proj_path);
      if ($proj_path_source) {
        $paths_to_check .= "\n" . $proj_path_source;
      }
      $proj_path_alias = _lookup_path_alias($proj_path);
      if ($proj_path_alias) {
        $paths_to_check .= "\n" . $proj_path_alias;
      }

      // See if there is a match against any variant of $proj_path.
      if (\Drupal::service('path.matcher')
        ->matchPath($current_path, $paths_to_check) || \Drupal::service('path.matcher')
        ->matchPath($current_path_alias, $paths_to_check)) {
        $add_snippet = TRUE;
        break 2;
      }
    }

    // foreach
  }
  if ($add_snippet) {

    // Add javascript call to page markup.
    $snippet_url = '//cdn.optimizely.com/js/' . $project['project_code'] . '.js';
    $page['#attached']['html_head'][] = [
      [
        '#tag' => 'script',
        '#attributes' => [
          'type' => 'text/javascript',
          'src' => $snippet_url,
        ],
        '#value' => '',
      ],
      'optimizely-snippet',
    ];
    $page['#cache']['tags'][] = $cache_tag;
  }

  // For every page, add all other cache tags that might possibly cause it
  // to be invalidated.
  // Site-wide wildcard.
  $page['#cache']['tags'][] = 'optimizely:*';

  // Non site-wide wildcards. Repeat for every directory level.
  $dirname = pathinfo($current_path, PATHINFO_DIRNAME);
  while ($dirname && $dirname != '/' && $dirname != '\\') {
    $page['#cache']['tags'][] = 'optimizely:' . $dirname . '/*';
    $dirname = pathinfo($dirname, PATHINFO_DIRNAME);
  }

  // The specific page url.
  $page['#cache']['tags'][] = 'optimizely:' . $current_path;

  // Finally, if there is an alias for the page, tag it.
  if ($current_path_alias) {
    $page['#cache']['tags'][] = 'optimizely:' . $current_path_alias;
  }
}

// The following three functions are redundant with and copied verbatim
// from trait LookupPath because I haven't been able to re-use the trait
// in this .module file.

/**
 * Helper function to lookup a path alias, given a path.
 *
 * This function acts as an adapter and passes back a return value
 * like those of drupal_lookup_path(), which has been removed
 * as of Drupal 8.
 */
function _lookup_path_alias($path) {
  $path = _check_path($path);
  $alias = \Drupal::service('path.alias_manager')
    ->getAliasByPath($path);
  return strcmp($alias, $path) == 0 ? FALSE : $alias;
}

/**
 * Helper function to lookup a system path, given a path alias.
 *
 * This function acts as an adapter and passes back a return value
 * like those of drupal_lookup_path(), which has been removed
 * as of Drupal 8.
 */
function _lookup_system_path($alias) {
  $alias = _check_path($alias);
  $path = \Drupal::service('path.alias_manager')
    ->getPathByAlias($alias);
  return strcmp($path, $alias) == 0 ? FALSE : $path;
}

/**
 * Ensure that $path starts with a forward slash.
 *
 * The alias_manager requires it.
 */
function _check_path($path) {
  return $path[0] == '/' ? $path : '/' . $path;
}

Functions

Namesort descending Description
optimizely_help Implements hook_help().
optimizely_page_attachments Implements hook_page_attachments().
optimizely_theme Implements hook_theme().
_check_path Ensure that $path starts with a forward slash.
_lookup_path_alias Helper function to lookup a path alias, given a path.
_lookup_system_path Helper function to lookup a system path, given a path alias.