You are here

acquia_lift.module in Acquia Lift Connector 7.2

acquia_lift.module Provides Acquia Lift-specific personalization functionality.

File

acquia_lift.module
View source
<?php

/**
 * @file acquia_lift.module
 * Provides Acquia Lift-specific personalization functionality.
 */
define('ACQUIA_LIFT_DEFAULT_AGENT_NAME', 'drupal-default');
define('ACQUIA_LIFT_OPERATION_ERROR_PREFIX', 'Errors: ');
define('ACQUIA_LIFT_AGENT_VERIFY_CACHE', 'acquia_lift:agent:verification');
define('ACQUIA_LIFT_TARGETING_EVERYONE_ELSE', 'everyone-else');
define('ACQUIA_LIFT_TARGETING_AGENT', 'acquia_lift_target');
define('ACQUIA_LIFT_TESTING_AGENT', 'acquia_lift_learn');
define('ACQUIA_LIFT_DECISION_LOCKSTEP', 'lockstep');
define('ACQUIA_LIFT_DECISION_MULTIVARIATE', 'mvt');
define('ACQUIA_LIFT_MULTIVARIATE_TEST_SUFFIX', '-mvt');
define('ACQUIA_LIFT_DOCUMENTATION', 'https://docs.acquia.com/lift/drupal');
define('ACQUIA_LIFT_INSPECTOR_URL', 'acquia_lift_inspect_mode');
define('ACQUIA_LIFT_DEFAULT_MAX_DAYS', 60);

/**
 * Implements hook_menu().
 */
function acquia_lift_menu() {
  $items = array();
  $items['admin/config/content/personalize/acquia_lift'] = array(
    'type' => MENU_LOCAL_TASK,
    'title' => 'Acquia Lift',
    'page callback' => 'acquia_lift_configuration_page',
    'access arguments' => array(
      'administer personalize configuration',
    ),
    'file' => 'acquia_lift.admin.inc',
  );
  $items['admin/acquia_lift'] = array(
    'title' => 'Acquia Lift',
    'description' => 'Manage Acquia Lift personalizations.',
    'position' => 'right',
    'weight' => 0,
    'page callback' => 'acquia_lift_root_page',
    'access arguments' => array(
      'manage personalized content',
    ),
    'file' => 'acquia_lift.ui.inc',
  );
  $items['acquia_lift/controls/assets'] = array(
    'page callback' => 'acquia_lift_controls_assets_callback',
    'access arguments' => array(
      'manage personalized content',
    ),
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.ui.inc',
  );

  // Ajax callback to provide updated campaign settings to JavaScript.
  $items['acquia_lift/settings'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'acquia_lift_settings_update',
    'access arguments' => array(
      'manage personalized content',
    ),
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
  );

  // Ajax callback to generate report data.
  $items['acquia_lift/reports/conversions-by-goal'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'acquia_lift_report_daily_conversions',
    'access arguments' => array(
      'manage personalized content',
    ),
    'file' => 'acquia_lift.admin.inc',
  );

  // AJAX callback to cancel a modal creation flow.
  $items['admin/structure/acquia_lift/cancel/%ctools_js'] = array(
    'page callback' => 'acquia_lift_campaign_flow_cancel',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );

  // AJAX callback to add a new variation set to a campaign.
  $items['admin/structure/acquia_lift/variations/add/%ctools_js'] = array(
    'page callback' => 'acquia_lift_option_set_add_modal_callback',
    'page arguments' => array(
      5,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );

  // AJAX callback to retrieve an element variation details form.
  $items['admin/structure/acquia_lift/variation/%'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'acquia_lift_element_variation_details_form',
      4,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'delivery callback' => 'ajax_deliver',
    'theme callback' => 'ajax_base_page_theme',
  );

  // AJAX callback to delete an element variation.
  $items['admin/structure/acquia_lift/variation/delete/%/%/%ctools_js'] = array(
    'page callback' => 'acquia_lift_element_variation_delete_modal_callback',
    'page arguments' => array(
      5,
      6,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );

  // Ajax callback to open the add a goal selection in a modal window.
  $items['admin/structure/acquia_lift/goal/add/%ctools_js'] = array(
    'page callback' => 'acquia_lift_goal_create_modal_callback',
    'page arguments' => array(
      5,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );

  // Ajax callback to create a goal of a specific type.
  $items['admin/structure/acquia_lift/goal/add/%/%ctools_js'] = array(
    'page callback' => 'acquia_lift_goal_type_create_modal_callback',
    'page arguments' => array(
      5,
      6,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );

  // Ajax callback to rename a goal.
  $items['admin/structure/acquia_lift/goal/rename/%/%ctools_js'] = array(
    'page callback' => 'acquia_lift_goal_rename_modal_callback',
    'page arguments' => array(
      5,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );

  // AJAX callback to delete a goal.
  $items['admin/structure/acquia_lift/goal/delete/%/%/%ctools_js'] = array(
    'page callback' => 'acquia_lift_goal_delete_modal_callback',
    'page arguments' => array(
      5,
      6,
    ),
    'access arguments' => array(
      'manage personalized content',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );
  $items['admin/structure/acquia_lift/visitor_action/%visitor_actions_custom'] = array(
    'page callback' => 'acquia_lift_visitor_action_edit_modal_callback',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'administer visitor actions',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.unibar.inc',
    'theme callback' => 'ajax_base_page_theme',
  );
  $items['admin/structure/personalize/manage/%personalize_agent/delete-all'] = array(
    'title' => 'Delete an agent and all its components',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'acquia_lift_agent_delete_form',
      4,
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.inc',
    'access callback' => 'acquia_lift_agent_delete_access',
    'access arguments' => array(
      4,
    ),
  );
  $items['admin/structure/personalize/manage/%personalize_agent/audience/%/delete'] = array(
    'title' => 'Delete target audience',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'acquia_lift_target_audience_delete',
      4,
      6,
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.wizard.inc',
    'access callback' => 'acquia_lift_target_access',
    'access arguments' => array(
      4,
    ),
  );
  $items['admin/structure/personalize/manage/%personalize_agent/audience/%/complete'] = array(
    'title' => 'Complete test for audience',
    'page callback' => 'acquia_lift_target_complete_audience_modal_callback',
    'page arguments' => array(
      4,
      6,
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.inc',
    'access callback' => 'acquia_lift_target_access',
    'access arguments' => array(
      4,
    ),
  );
  $items['admin/structure/personalize/manage/%personalize_agent/targeting/revert'] = array(
    'title' => 'Revert targeting changes',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'acquia_lift_confirm_revert_changes',
      4,
    ),
    'access callback' => 'acquia_lift_target_access',
    'access arguments' => array(
      4,
    ),
    'type' => MENU_CALLBACK,
    'file' => 'acquia_lift.admin.wizard.inc',
  );
  $items['admin/structure/personalize/manage/%personalize_agent/results'] = array(
    'title callback' => 'personalize_campaign_title_callback',
    'title arguments' => array(
      4,
    ),
    'page callback' => 'acquia_lift_report_wizard',
    'page arguments' => array(
      4,
    ),
    'access callback' => 'acquia_lift_target_access',
    'access arguments' => array(
      4,
    ),
    'file' => 'acquia_lift.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function acquia_lift_menu_alter(&$items) {

  // Change the access callback for the edit campaign page so that we can control
  // access to Lift campaigns.
  $items['admin/structure/personalize/manage/%personalize_agent/edit']['access callback'] = 'acquia_lift_campaign_edit_access';
  $items['admin/structure/personalize/manage/%personalize_agent/edit']['access arguments'] = array(
    4,
  );

  // Take over the rendering for the campaign listing page.
  $items['admin/structure/personalize']['page callback'] = 'acquia_lift_agent_list';
  $items['admin/structure/personalize']['file'] = 'acquia_lift.admin.inc';
  $items['admin/structure/personalize']['module'] = 'acquia_lift';
}

/**
 * Implements hook_theme().
 */
function acquia_lift_theme() {
  $path = drupal_get_path('module', 'acquia_lift');
  return array(
    'acquia_lift_personalizable_field_form' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
    ),
    'acquia_lift_personalize_field_weight_field_wrapper' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
    ),
    'acquia_lift_edit_mode_personalize_in_context_links' => array(
      'render element' => 'elements',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
    ),
    'acquia_lift_feature_filter_links' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
    ),
    'acquia_lift_percentage' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
    ),
    'acquia_lift_report_overview' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
    ),
    'acquia_lift_high_low' => array(
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
      'high' => 0,
      'low' => 0,
      'value' => 0,
    ),
    'acquia_lift_goal_total' => array(
      'goal_total' => 0,
    ),
    // Campaign creation flow.
    'acquia_lift_type_list' => array(
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
      'items' => array(),
    ),
    'acquia_lift_create_type_change' => array(
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
      'type' => '',
      'change_link' => '',
    ),
    'acquia_lift_card' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
    ),
    'acquia_lift_add_card_button' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
    ),
    'acquia_lift_radio_list' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
      'preprocess functions' => array(
        'template_preprocess_acquia_lift_radio_list',
      ),
    ),
    'acquia_lift_radio_list_item' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
    ),
    'acquia_lift_revealing_input' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
    ),
    'acquia_lift_variations_list' => array(
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
      'items' => array(),
    ),
    'acquia_lift_single_variation' => array(
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
      'option' => '',
      'option_set' => '',
    ),
    'acquia_lift_wizard_section_help' => array(
      'path' => $path . '/theme',
      'file' => 'acquia_lift.wizard.theme.inc',
      'render element' => 'element',
    ),
    // Unified navbar themes.
    'acquia_lift_navbar' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.navbar.theme.inc',
    ),
    'acquia_lift_navbar_item' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.navbar.theme.inc',
    ),
    'acquia_lift_navbar_tray_wrapper' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.navbar.theme.inc',
    ),
    'acquia_lift_navbar_tray_heading_wrapper' => array(
      'render element' => 'element',
      'path' => $path . '/theme',
      'file' => 'acquia_lift.navbar.theme.inc',
    ),
    'menu_tree__acquia_lift_controls' => array(
      'render element' => 'tree',
      'function' => 'theme_acquia_lift_navbar_menu_tree',
      'preprocess functions' => array(
        'template_preprocess_acquia_lift_navbar_menu_tree',
      ),
    ),
    'acquia_lift_debugger' => array(
      'path' => $path . '/theme',
      'file' => 'acquia_lift.theme.inc',
    ),
  );
}

/**
 * Implements hook_init().
 */
function acquia_lift_init() {
  if (!empty($_SESSION['acquia_lift_element_trigger']) && user_access('manage personalized content')) {

    // Element variation editing should enabled upon page load.
    drupal_add_js(array(
      'acquia_lift' => array(
        'toolbarEditMode' => $_SESSION['acquia_lift_element_trigger'],
      ),
    ), array(
      'type' => 'setting',
    ));
    unset($_SESSION['acquia_lift_element_trigger']);
  }
}

/**
 * Implements hook_page_build().
 */
function acquia_lift_page_build(&$page) {
  $page['page_top']['#attached']['library'][] = array(
    'acquia_lift',
    'acquia_lift.page',
  );
  if (user_access('manage personalized content')) {
    module_load_include('inc', 'acquia_lift', 'acquia_lift.ui');
    acquia_lift_build_page($page);
  }
  if (!path_is_admin(current_path()) && acquia_lift_debug_mode_enabled()) {
    $page['footer']['acquia_lift_debug'] = array(
      '#attached' => array(
        'library' => array(
          array(
            'acquia_lift',
            'debugger',
          ),
        ),
      ),
      'container' => array(
        '#markup' => theme('acquia_lift_debugger'),
      ),
    );
  }
}

/**
 * Implements hook_element_info().
 *
 * @see navbar_element_info().
 */
function acquia_lift_element_info() {
  $elements = array();

  // A percentage input element for entering percentage values and seeing
  // the resultant percent left.
  $elements['acquia_lift_percentage'] = array(
    '#input' => TRUE,
    '#element_validate' => array(
      'element_validate_number',
      'acquia_lift_percentage_validate',
    ),
    '#theme' => 'acquia_lift_percentage',
  );

  // Elements necessary for the unified navbar.
  // Note that we keep the CSS selectors the same as those used by navbar
  // in order to use the same set of CSS.
  $elements['acquia_lift_navbar'] = array(
    '#pre_render' => array(
      'acquia_lift_navbar_pre_render',
    ),
    '#theme' => 'acquia_lift_navbar',
    '#attached' => array(
      'library' => array(
        array(
          'acquia_lift',
          'acquia_lift.unified.navbar',
        ),
      ),
    ),
    // Metadata for the navbar wrapping element.
    '#attributes' => array(
      // The id cannot be simply "navbar" or it will clash with the simpletest
      // tests listing which produces a checkbox with attribute id="navbar"
      'id' => 'navbar-administration',
      // The 'overlay-displace-top' class is necessary in overlay-parent so that
      // the drupalOverlayResize and drupalOverlayClose events will be bound
      // to the document. The navbar does not use this class. It is present
      // to enable compatibility with the Overlay module.
      'class' => array(
        'drupal-navbar',
        'overlay-displace-top',
      ),
      'role' => 'navigation',
    ),
    // Metadata for the administration bar.
    '#bar' => array(
      '#heading' => t('Acquia Lift Navigation'),
      '#attributes' => array(
        'id' => 'navbar-bar',
        'class' => array(
          'navbar-bar',
          'clearfix',
        ),
      ),
    ),
  );

  // A navbar item is wrapped in markup for common styling.  The 'tray'
  // property contains a renderable array. theme_acquia_lift_navbar_tab() is a light
  // wrapper around the l() function. The contents of tray are rendered in
  // theme_acquia_lift_navbar_tab().
  $elements['acquia_lift_navbar_item'] = array(
    '#pre_render' => array(
      'acquia_lift_navbar_pre_render_item',
    ),
    '#theme' => 'acquia_lift_navbar_item',
  );
  return $elements;
}

/**
 * Element validator for acquia_lift_percentage custom element.
 *
 * Values submitted as percentages must be numbers between 0 and 100.
 * It is assumed that the value is already validated as a number.
 *
 * @see acquia_lift_element_info().
 */
function acquia_lift_percentage_validate($element, &$form_state) {
  $value = $element['#value'];
  if ($value < 0 || $value > 100) {
    form_error($element, t('%name must be a number between 0 and 100.', array(
      '%name' => $element['#title'],
    )));
  }
}

/**
 * Retrieve the Acquia Lift Credentials.
 * @return array
 *   The value of the acquia_lift_account_info variable together with acquia
 *   connector credentials.
 */
function acquia_lift_get_account_info() {
  $account_info = array(
    'public_key' => '',
    'private_key' => '',
    'profiles' => array(
      'account_name' => variable_get('acquia_lift_profiles_account_name', ''),
      'hostname' => '',
      'public_key' => '',
      'secret_key' => '',
    ),
  );

  // Get Acquia Subscription Data
  $subscription_data = acquia_agent_settings('acquia_subscription_data');
  $acquia_lift_data = array();
  if (isset($subscription_data['heartbeat_data']['acquia_lift'])) {
    $acquia_lift_data = $subscription_data['heartbeat_data']['acquia_lift'];
    $default_lift_data = array(
      'status' => false,
      'decision' => array(
        'public_key' => '',
        'private_key' => '',
        'api_url' => '',
      ),
      'profile' => array(
        'js_path' => '',
        'account_name' => '',
        'hostname' => '',
        'private_key' => '',
        'secret_key' => '',
      ),
    );

    // make sure there is always the base set that we can rely on. This
    // prevents us from forcing an isset before working with the data.
    $acquia_lift_data = array_merge($default_lift_data, $acquia_lift_data);
  }

  // Set default url
  $account_info['api_url'] = variable_get('acquia_lift_api_url', '');

  // Find out if there is Acquia Lift information in the provided subscription information.
  if (isset($acquia_lift_data['status']) && $acquia_lift_data['status'] == TRUE) {
    foreach ($acquia_lift_data['decision'] as $key => $value) {
      switch ($key) {
        case "public_key":
        case "private_key":

          // If it was overridden in settings.php - do not override again
          // with Acquia Network creds.
          $account_info[$key] = empty($account_info[$key]) ? $value : $account_info[$key];
          break;
      }
    }
    if (empty($account_info['api_url'])) {
      if (empty($acquia_lift_data['decision']['hostname'])) {
        $account_info['api_url'] = 'us-east-1-decision.lift.acquia.com';
      }
      else {
        $account_info['api_url'] = $acquia_lift_data['decision']['hostname'];
      }
    }
    foreach ($acquia_lift_data['profile'] as $key => $value) {
      switch ($key) {
        case "js_path":
          $variable_name = 'acquia_lift_profiles_js_path';

          // Strip any http/https prefix
          $value = preg_replace('/(^[a-z]+:\\/\\/)/i', '', $value);

          // js path is a special case as we require it also for regular
          // Acquia Lift. Therefor we add it to our main data set as an
          // exception. If it was overridden in settings.php - do not
          // override again with Acquia Network creds.
          $account_info[$key] = empty($account_info[$key]) ? $value : $account_info[$key];
          break;
        case "account_name":
          $variable_name = 'acquia_lift_profiles_account_name';
          break;
        case "hostname":
          $variable_name = 'acquia_lift_profiles_api_url';
          break;
        case "public_key":
          $variable_name = 'acquia_lift_profiles_access_key';
          break;
        case "secret_key":
          $variable_name = 'acquia_lift_profiles_secret_key';
          break;
        default:
          $variable_name = false;
          break;
      }

      // Set variable if it was not set.
      if ($variable_name) {
        _acquia_lift_set_variable_if_not_set($variable_name, $value);
        $account_info['profiles'][$key] = empty($account_info['profiles'][$key]) ? $value : $account_info['profiles'][$key];
      }
    }

    // unset the js_path from profiles since that is a special case
    // @todo Get rid of the special case...
    unset($account_info['profiles']['js_path']);
  }

  // Whether or not to check the response value.
  $account_info['validate_response'] = variable_get('acquia_lift_validate_response', TRUE);
  return $account_info;
}

/**
 * Helper function to set a non existing variable. This is needed
 * during clean installs of acquia lift where the variables are not set yet
 * and we want to set them with a default value. We can't do this during
 * installation because the Acquia Connector is not set yet.
 *
 * @param $variable_name
 *    The name of the variable
 * @param $variable_value
 *    The value of the variable
 */
function _acquia_lift_set_variable_if_not_set($variable_name, $variable_value) {

  // @todo remove the variable set when we no longer need the variable but
  // are able to read it in from the account_info array
  $existing_var = variable_get($variable_name, '');

  // Set our variable if the variable was not set yet.
  if ($existing_var == '') {
    variable_set($variable_name, $variable_value);
  }
}

/*
 * Implements hook_flush_caches().
 */
function acquia_lift_flush_caches() {
  return array(
    'cache_acquia_lift_reports',
  );
}

/**
 * Returns the agent types this module provides.
 *
 * @return array
 *   An associative array with agent plugin names as keys and class names
 *   as values.
 */
function acquia_lift_get_agent_types() {
  $types = array(
    'acquia_lift_target' => array(
      'class' => 'AcquiaLiftTarget',
      'file' => 'AcquiaLiftTarget.inc',
    ),
    ACQUIA_LIFT_TESTING_AGENT => array(
      'class' => 'AcquiaLiftLearn',
      'file' => 'AcquiaLiftLearn.inc',
    ),
  );
  return $types;
}

/**
 * Returns whether or not the passed in agent is a testing agent.
 *
 * @param $agent
 *   An object representing the agent.
 * @return bool
 *   TRUE if the object is an agent whose plugin is one of the testing plugins
 *   provided by this module, FALSE otherwise.
 */
function acquia_lift_is_testing_agent($agent) {
  if (empty($agent)) {
    return FALSE;
  }
  return $agent->plugin == ACQUIA_LIFT_TESTING_AGENT;
}

/**
 * Returns whether or not the passed in agent is a targeting agent.
 *
 * @param $agent
 *   An object representing the agent.
 * @return bool
 *   TRUE if the object is an Acquia Lift targeting agent, FALSE otherwise.
 */
function acquia_lift_is_targeting_agent($agent) {
  if (empty($agent)) {
    return FALSE;
  }
  return $agent->plugin == 'acquia_lift_target';
}
function acquia_lift_get_mvt_name_for_agent($agent_name) {
  return $agent_name . ACQUIA_LIFT_MULTIVARIATE_TEST_SUFFIX;
}

/**
 * Implements hook_personalize_agent_type().
 */
function acquia_lift_personalize_agent_type() {
  $info = array();
  $account_info = acquia_lift_get_account_info();

  // Only return plugin info if account has been setup. If not an
  // exception is thrown. Not to mention it's extremely confusing if it's the
  // only personalize_agent_type available.
  if (!empty($account_info)) {
    $path = drupal_get_path('module', 'acquia_lift') . '/plugins/agent_types';
    foreach (acquia_lift_get_agent_types() as $type_name => $handler) {
      $info[$type_name] = array(
        'path' => $path,
        'handler' => $handler,
      );
    }
  }
  return $info;
}

/**
 * Implements hook_personalize_wizard_steps_alter().
 */
function acquia_lift_personalize_wizard_steps_alter(&$steps, $agent_instance) {
  if (empty($agent_instance) || !$agent_instance instanceof AcquiaLiftTarget) {
    return;
  }
  $steps['results'] = array(
    'label' => t('Reports'),
    'description' => t('Review graphs and tables of personalization performance'),
    'button_value' => t('Next'),
  );
}

/**
 * Access callback for the Lift Target menu items.
 *
 * @return bool
 */
function acquia_lift_target_access($agent) {
  return acquia_lift_is_targeting_agent($agent) && user_access('manage personalized content');
}

/**
 * Access callback for the Lift legacy campaign list.
 *
 * @return bool
 */
function acquia_lift_legacy_access() {
  $legacy_campaigns = variable_get('acquia_lift_legacy_agents', array());
  return user_access('manage personalized content') && !empty($legacy_campaigns);
}

/**
 * Access callback for campaign edit page.
 */
function acquia_lift_campaign_edit_access($agent) {
  if ($agent->plugin === 'acquia_lift') {
    return FALSE;
  }
  return user_access('manage personalized content');
}

/**
 * Access callback for agent deletion.
 */
function acquia_lift_agent_delete_access($agent) {

  // Personalize module is more restricted in what it allows deletion of it,
  // so if we personalize module allows deletion, then so do we.
  if (personalize_delete_agent_access($agent)) {
    return TRUE;
  }
  $delete_allowed = FALSE;

  // We also allow outright deletion of agents that are legacy agents, regardless
  // of status, or nested tests that have been retired.
  $legacy_campaigns = variable_get('acquia_lift_legacy_agents', array());
  if (in_array($agent->machine_name, $legacy_campaigns)) {
    $delete_allowed = TRUE;
  }
  else {
    module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
    $retired_tests = acquia_lift_get_retired_tests();
    foreach ($retired_tests as $parent => $tests) {
      foreach ($tests as $test) {
        if ($agent->machine_name == $test->machine_name) {

          // This is a retired test and can be deleted.
          $delete_allowed = TRUE;
          break 2;
        }
      }
    }
  }
  return user_access('manage personalized content') && $delete_allowed;
}

/**
 * Implements hook_personalize_agent_presave().
 */
function acquia_lift_personalize_agent_presave($agent) {
  if ($agent->plugin !== ACQUIA_LIFT_TESTING_AGENT) {
    return;
  }
  $current = personalize_agent_load($agent->machine_name);
  if (empty($current)) {
    $fail_msg = t('There is a problem with your Lift credentials. Personalizations will not work until this has been resolved.');
    try {
      $account_info = acquia_lift_get_account_info();
      $lift_api = AcquiaLiftAPI::getInstance($account_info);
      $agent->machine_name = $lift_api
        ->ensureUniqueAgentName($agent->machine_name, PERSONALIZE_MACHINE_NAME_MAXLENGTH);
    } catch (AcquiaLiftCredsException $e) {

      // The creds haven't been set up yet, so it's probably safe to go with the
      // given machine name, but set an error message for the user.
      drupal_set_message($fail_msg, 'error');
    } catch (AcquiaLiftForbiddenException $e) {

      // Creds have been configured but not correctly, so again it is probably
      // ok to go ahead, but set an error message for the user.
      drupal_set_message($fail_msg, 'error');
    }

    // Save the Lift Web site name to the test agent as it will be used as a
    // prefix to disambiguate goals and decision sets.
    $site_name = variable_get('acquia_lift_profiles_site_name', 'drupal');
    $agent->data['site_name'] = personalize_generate_machine_name($site_name);
  }

  // Make sure the required configurations are set.
  if (!isset($agent->data['decision_style'])) {
    $agent->data['decision_style'] = 'random';
  }
  if (!isset($agent->data['control_rate'])) {
    $agent->data['control_rate'] = 10;
  }
  if (!isset($agent->data['explore_rate'])) {
    $agent->data['explore_rate'] = 20;
  }
}

/**
 * Implements hook_personalize_agent_update_status().
 */
function acquia_lift_personalize_agent_update_status($agent_name, $old_status, $status) {
  $agent = personalize_agent_load($agent_name);
  if (!acquia_lift_is_targeting_agent($agent)) {
    return;
  }
  $nested = acquia_lift_get_nested_tests($agent);
  foreach ($nested as $test_agent) {
    personalize_agent_set_status($test_agent, $status);
  }
}

/**
 * Implements hook_personalize_agent_delete().
 */
function acquia_lift_personalize_agent_delete($agent) {
  if (!acquia_lift_is_testing_agent($agent)) {
    return;
  }
  $account_info = acquia_lift_get_account_info();
  try {
    $lift_api = AcquiaLiftAPI::getInstance($account_info);
    $lift_api
      ->deleteAgent($agent->machine_name);
  } catch (Exception $e) {
  }
}

/**
 * Implements hook_personalize_goal_presave().
 */
function acquia_lift_personalize_goal_presave($goal) {
  $agent = personalize_agent_load($goal->agent);
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  if (!acquia_lift_target_definition_changes_allowed($agent)) {
    throw new PersonalizeException(t('Goals cannot be modified until the personalization is paused.'));
  }
}

/**
 * Implements hook_personalize_option_set_presave().
 */
function acquia_lift_personalize_option_set_presave($option_set) {

  // If the everyone-else audience is the only audience, name it "Everyone";
  // otherwise name it "Everyone else".
  if (!empty($option_set->targeting) && ($fallback_audience_id = acquia_lift_get_fallback_audience_name(array_keys($option_set->targeting)))) {
    $fallback_audience_name = count($option_set->targeting) > 1 ? t('Everyone else') : t("Everyone");
    $option_set->targeting[$fallback_audience_id]['label'] = $fallback_audience_name;
  }

  // The rest only applies to saving of existing option sets.
  if (empty($option_set->osid)) {
    return;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  $agent_data = personalize_agent_load($option_set->agent);
  if (acquia_lift_target_definition_changes_allowed($agent_data)) {
    return;
  }
  $original_option_set = personalize_option_set_load($option_set->osid);
  $new_option_ids = array_map(function ($option) {
    return $option['option_id'];
  }, $option_set->options);
  $original_option_ids = array_map(function ($option) {
    return $option['option_id'];
  }, $original_option_set->options);
  $removed = array_diff($original_option_ids, $new_option_ids);
  foreach ($removed as $option_id) {
    if (acquia_lift_target_option_targeted($agent_data->machine_name, $option_id)) {
      throw new PersonalizeException(t('Variations used in targeting cannot be removed until the personalization is paused.'));
    }
  }
}

/**
 * Implements hook_personalize_option_set_render().
 */
function acquia_lift_personalize_option_set_render(&$element, $option_set) {

  // For administrative users and Acquia Lift target agents, indicate if option
  // sets are deletable (only applicable to personalize elements sets).
  if (user_access('manage personalized content')) {
    $option_set_settings = acquia_lift_get_option_set_editable_settings($option_set);
    $element['#attached']['js'][] = array(
      'type' => 'setting',
      'data' => $option_set_settings,
    );
  }
  if (empty($option_set->targeting)) {
    return;
  }
  $rules = $option_set->targeting;

  // We add our own settings for nested agents as they won't have been added
  // by personalize.
  $settings = acquia_lift_target_default_js_settings($option_set->agent);
  $assets = array(
    'js' => array(),
  );
  foreach ($rules as $rule) {
    if (isset($rule['option_id']) || !isset($rule['osid'])) {
      continue;
    }
    $child_os = personalize_option_set_load($rule['osid']);

    // Make sure the agent assets are available for the child OS.
    $nested_agent = personalize_agent_load($child_os->agent);

    // If the test agent has a site name property, then this is used as a prefix
    // for decision and goal names.
    $site_name_prefix = !empty($nested_agent->data['site_name']) ? $nested_agent->data['site_name'] . '-' : '';
    $child_settings = personalize_agent_get_map_settings($child_os->agent, $child_os, $assets);
    $child_settings['site_name_prefix'] = $site_name_prefix;
    $settings['agent_map'][$child_os->agent] = $child_settings;
    $settings['option_sets'] += _personalize_convert_option_set_to_js_setting($child_os);
  }
  $settings_array = array(
    'type' => 'setting',
    'data' => array(
      'acquia_lift_target' => $settings,
    ),
  );
  $assets['js'] = array_merge_recursive($assets['js'], array(
    $settings_array,
  ));
  personalize_merge_element_assets($element, $assets);
}

/**
 * Returns the basic js settings needed by acquia_lift_target.
 * @param null $agent_name
 *   An optional agent to include nested_tests for.
 * @return array
 */
function acquia_lift_target_default_js_settings($agent_name = NULL) {
  $settings = array(
    'default_target' => ACQUIA_LIFT_TARGETING_EVERYONE_ELSE,
    'test_agent_plugin' => ACQUIA_LIFT_TESTING_AGENT,
    'agent_map' => array(),
    'option_sets' => array(),
    'nested_tests' => array(),
  );
  if (!empty($agent_name)) {
    $settings['nested_tests'][$agent_name] = array();
    $agent = personalize_agent_load($agent_name);
    $nested_tests = acquia_lift_get_nested_tests($agent);
    foreach ($nested_tests as $test) {
      $settings['nested_tests'][$agent_name][$test] = $test;
    }
  }
  return $settings;
}

/**
 * Helper function to generate editable JavaScript settings for an option set.
 *
 * @param stdClass $option_set
 *   The option set to generate settings for.
 * @return array
 *   An array of settings to be added to JavaScript.
 */
function acquia_lift_get_option_set_editable_settings($option_set) {
  $js_osid = personalize_stringify_osid($option_set->osid);
  $option_set_settings = array();
  if ($option_set->plugin === 'elements') {

    // Personalize elements are always editable, but only deletable if not
    // used in targeting in a running/competed campaign.
    $agent_data = personalize_agent_load($option_set->agent);
    if ($agent_data->plugin === 'acquia_lift_target') {
      module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
      $changes_enabled = acquia_lift_target_definition_changes_allowed($agent_data);
      foreach ($option_set->options as $option) {
        $is_control = $option['option_id'] === PERSONALIZE_CONTROL_OPTION_ID;
        $option_set_settings['acquia_lift']['option_sets'][$js_osid][$option['option_id']]['deletable'] = !$is_control && ($changes_enabled || !acquia_lift_target_option_targeted($agent_data->machine_name, $option['option_id']));
        $option_set_settings['acquia_lift']['option_sets'][$js_osid][$option['option_id']]['editable'] = !$is_control;
      }
    }
  }
  else {

    // Other types of options are not editable or deletable from the unibar.
    foreach ($option_set->options as $option) {
      $option_set_settings['acquia_lift']['option_sets'][$js_osid][$option['option_id']]['deletable'] = FALSE;
      $option_set_settings['acquia_lift']['option_sets'][$js_osid][$option['option_id']]['editable'] = FALSE;
    }
  }
  return $option_set_settings;
}

/**
 * Validate a set of option sets to see if they are valid for lock-step
 * handling.
 *
 * @param array $option_sets
 *   An array of option set stdClass objects to validate.
 * @return boolean
 *   TRUE if the option sets are valid for lock-step, false if otherwise.
 */
function acquia_lift_target_validate_lock_step($option_sets) {
  $equal_option_counts = TRUE;
  if (count($option_sets) > 1) {
    $first_os = array_shift($option_sets);
    $num_options = count($first_os->options);
    foreach ($option_sets as $os) {
      if (count($os->options) != $num_options) {
        $equal_option_counts = FALSE;
      }
    }
  }
  return $equal_option_counts;
}

/**
 * Implements hook_personalize_option_set_save().
 */
function acquia_lift_personalize_option_set_save($option_set) {
  $agent = personalize_agent_load($option_set->agent);
  if (!acquia_lift_is_targeting_agent($agent)) {
    return;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  acquia_lift_validate_minimum_targeting($agent, $option_set);
}

/**
 * Implements hook_personalize_option_set_insert().
 */
function acquia_lift_personalize_option_set_insert($option_set) {
  $agent = personalize_agent_load($option_set->agent);
  if (!acquia_lift_is_targeting_agent($agent)) {
    return;
  }

  // Create a goal for hte option set. If it's a fields-based option set we first
  // check whether this is the configured behavior.
  if ($option_set->plugin == 'fields') {
    $field = field_info_field($option_set->data['personalize_fields_field_name']);
    if (!isset($field['settings']['personalize']) || !$field['settings']['personalize']['enabled'] || !isset($field['settings']['personalize']['create_goal']) || !$field['settings']['personalize']['create_goal']) {
      return;
    }
  }
  elseif (!variable_get('acquia_lift_auto_goal', TRUE)) {
    return;
  }

  // Create a goal for every inserted option set.
  $js_id = personalize_stringify_osid($option_set->osid);
  $selector = isset($option_set->data['personalize_elements_selector']) ? $option_set->data['personalize_elements_selector'] : '[data-personalize=' . $js_id . ']';
  $plugin = 'link';
  $action_label = t('Clicks') . ' ' . personalize_sanitize_string($option_set->label);
  $action_name = personalize_generate_machine_name($action_label, 'visitor_actions_machine_name_exists', '_');
  $pages = empty($field['settings']['personalize']['goal_pages']) ? '' : $field['settings']['personalize']['goal_pages'];
  $action = array(
    'label' => $action_label,
    'machine_name' => $action_name,
    'plugin' => $plugin,
    'client_side' => 1,
    'identifier' => $selector,
    'event' => 'click',
    'pages' => $pages,
    'data' => array(
      'auto_created' => $option_set->osid,
    ),
    'limited_use' => 1,
  );

  // Allow the plugin to modify the action before saving.
  if ($class = ctools_plugin_load_class('visitor_actions', 'actionable_element', $plugin, 'handler')) {
    $action = call_user_func_array(array(
      $class,
      'actionPresave',
    ), array(
      $action,
    ));
  }
  if (visitor_actions_save_action($action)) {
    personalize_goal_save($option_set->agent, $action_name, 1);
  }
}

/**
 * Implements hook_personalize_option_set_delete().
 */
function acquia_lift_personalize_option_set_delete($option_set) {
  $agent = personalize_agent_load($option_set->agent);
  if (acquia_lift_is_targeting_agent($agent) && !empty($option_set->targeting)) {
    $option_sets = personalize_option_set_load_by_agent($option_set->agent, TRUE);
    if (empty($option_sets)) {

      // Ensure any nested tests are deleted too
      foreach ($option_set->targeting as $target) {
        if (isset($target['osid'])) {
          $nested_os = personalize_option_set_load($target['osid'], TRUE);

          // Delete the option set, then delete the agent.
          personalize_option_set_delete($nested_os->osid);
          personalize_agent_delete($nested_os->agent);
        }
      }
    }
    if (!empty($agent->data['locked_for_variation_sets'])) {

      // Somehow an option set is being deleted for an already implemented campaign.
      // This is not supported via the UI and can cause problems for running
      // campaigns. Set the campaign's status to completed to avoid any problematic
      // decision calls being made.
      personalize_agent_set_status($agent->machine_name, PERSONALIZE_STATUS_COMPLETED);
      drupal_set_message(t('The @agent_name personalization has been set to completed because one of its variation sets was deleted', array(
        '@agent_name' => $agent->machine_name,
      )), 'warning');
    }
  }
}

/**
 * Queue callback function for making a request to Acquia Lift.
 *
 * @param $item
 *   The queue item to process. It should be an array with the following keys:
 *   - method The method to call on the AcquiaLiftAPI instance
 *   - args The args to pass to the method.
 */
function acquia_lift_sync_item($item) {

  // The item is either a method to be called on the LiftAPI class or a regular
  // function.
  // @todo This conditional is a bit gross, figure out a way to handle the different types
  //   of callables better.
  if (isset($item['method'])) {
    $account_info = acquia_lift_get_account_info();
    $acquia_lift_api = AcquiaLiftAPI::getInstance($account_info);
    call_user_func_array(array(
      $acquia_lift_api,
      $item['method'],
    ), $item['args']);
  }
  else {
    call_user_func_array($item['callback'], $item['args']);
  }
}

/**
 * Wrapper function around acquia_lift_sync_item that catches and logs exceptions thrown.
 *
 * @param $item
 *   The item to process
 * @param &$errors
 *   An array passed by reference to add any errors to.
 *
 */
function acquia_lift_batch_sync_item($item, &$errors = array()) {
  try {
    acquia_lift_sync_item($item);
  } catch (AcquiaLiftException $e) {
    watchdog('Acquia Lift', 'Could not call the method @method with args @args', array(
      '@method' => $item['method'],
      '@args' => implode(',', $item['args']),
    ));
    $errors[] = $e
      ->getMessage();
  }
}

/**
 * Implements hook_library().
 */
function acquia_lift_library() {
  $path = drupal_get_path('module', 'acquia_lift');
  $options = array(
    'scope' => 'footer',
    'defer' => TRUE,
  );

  // Acquia lift campaign management toolbar.
  $libraries['acquia_lift.personalize'] = array(
    'title' => 'Acquia lift navigation',
    'website' => '',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.personalize.js' => $options,
      array(
        'data' => array(
          'personalize' => array(
            'links' => array(
              'campaigns' => array(
                'getActive' => url('personalize/campaign_context'),
                'setActive' => url('personalize/campaign_context/') . '%personalize_agent',
              ),
            ),
          ),
        ),
        'type' => 'setting',
      ),
    ),
    'css' => array(
      $path . '/css/acquia_lift.personalize.css' => array(),
      $path . '/css/acquia_lift.personalize.theme.css' => array(),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
      array(
        'system',
        'drupal.ajax',
      ),
      array(
        'acquia_lift',
        'underscore',
      ),
      array(
        'acquia_lift',
        'backbone',
      ),
      array(
        'visitor_actions_ui',
        'utilities.backbone_parent',
      ),
    ),
  );
  $js_path = variable_get('acquia_lift_profiles_js_path', '');
  if (!empty($js_path)) {
    $account_name = variable_get('acquia_lift_profiles_account_name', '');
    $customer_site = variable_get('acquia_lift_profiles_site_name', 'Drupal');

    // Initialize the queues.
    $init = <<<EOT
var _tcaq = _tcaq || [];
var _tcwq = _tcwq || [];
EOT;
    if (!empty($account_name)) {
      $init .= <<<EOT

_tcaq.push(['setAccount', '{<span class="php-variable">$account_name</span>}', '{<span class="php-variable">$customer_site</span>}']);
EOT;
    }

    // Add the capture script.
    global $is_https;
    $url_scheme = $is_https ? 'https://' : 'http://';
    $tc_js = $url_scheme . $js_path;
    $libraries['acquia_lift.page'] = array(
      'title' => 'Adds Acquia Lift page processing necessary for all page views.',
      'version' => VERSION,
      'js' => array(
        array(
          'data' => $init,
          'type' => 'inline',
          'weight' => JS_DEFAULT - 6,
        ),
        $tc_js => array(
          'preprocess' => FALSE,
          'weight' => JS_DEFAULT - 5,
          'scope' => 'footer',
        ),
        $path . '/js/acquia_lift.page.js' => $options,
        $path . '/js/acquia_lift.goals_queue.js' => $options,
        array(
          'data' => array(
            'acquia_lift' => array(
              'api_class' => 'acquiaLiftAPI',
              'account_name' => $account_name,
              'customer_site' => $customer_site,
            ),
            'personalize' => array(
              'cacheExpiration' => array(
                'lift' => 'session',
              ),
            ),
          ),
          'type' => 'setting',
        ),
      ),
      'dependencies' => array(
        array(
          'system',
          'jquery',
        ),
        array(
          'system',
          'jquery.once',
        ),
      ),
    );
  }
  else {
    drupal_set_message(t("Your Lift js path has not been configured. Personalization will not work."), 'error');
  }
  $account_info = acquia_lift_get_account_info();
  try {
    $lift_api = AcquiaLiftAPI::getInstance($account_info);
    $libraries['acquia_lift.page']['dependencies'][] = array(
      'acquia_lift',
      'acquia_lift.agent_api',
    );
    $site_name = variable_get('acquia_lift_profiles_site_name', '');
    if (empty($site_name)) {
      $site_name = 'drupal';
    }
    $libraries['acquia_lift.agent_api'] = array(
      'title' => 'Adds the Acquia Lift API files.',
      'version' => VERSION,
      'js' => array(
        $path . '/js/acquia_lift.api.js' => $options,
        array(
          'data' => array(
            'acquia_lift_learn' => array(
              'baseUrl' => $lift_api
                ->getApiUrl(),
              'clientId' => $lift_api
                ->getPublicKey(),
              'applicationHash' => $site_name,
            ),
          ),
          'type' => 'setting',
        ),
      ),
      'dependencies' => array(
        array(
          'system',
          'jquery',
        ),
      ),
    );
  } catch (\Exception $e) {

    // do nothing. This fails when there are no keys present.
  }
  $libraries['acquia_lift.fields'] = array(
    'title' => 'Add navigation to personalizable fields',
    'website' => '',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.fields.js' => $options,
    ),
    'css' => array(
      $path . '/css/acquia_lift.fields.theme.css' => array(),
      $path . '/css/acquia_lift.fields.admin.css' => array(),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
    ),
  );

  // Note that there is an additional CSS file: acquia_lift.ctools.modal.css.
  // It needs to be loaded separately in order to be processed after CTools
  // default modal CSS.
  $libraries['acquia_lift.modal'] = array(
    'title' => 'Acquia Lift modal personalization management',
    'website' => '',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.flow.js' => $options,
      array(
        'data' => array(
          'acquia-lift-style' => array(
            'modalSize' => array(
              'type' => 'dynamic',
              'width' => 450,
              'addWidth' => 0,
              'addHeight' => 0,
              'contentRight' => 0,
              'contentBottom' => 0,
            ),
            'modalTheme' => 'AcquiaLiftModalDialog',
            'modalOptions' => array(
              'background-color' => '#000000',
            ),
            'closeImage' => theme('image', array(
              'path' => ctools_image_path('close.png', 'acquia_lift'),
              'alt' => t('Close window'),
              'title' => t('Close window'),
            )),
            'closeText' => '',
          ),
        ),
        'type' => 'setting',
      ),
    ),
    'css' => array(
      $path . '/css/acquia_lift.buttons.css' => array(),
      $path . '/css/acquia_lift.dialog.css' => array(),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
      array(
        'system',
        'drupal.states',
      ),
      array(
        'acquia_lift',
        'underscore',
      ),
      array(
        'acquia_lift',
        'backbone',
      ),
      array(
        'visitor_actions_ui',
        'ui.element_dialog',
      ),
      array(
        'visitor_actions_ui',
        'utilities.element_selector',
      ),
      array(
        'acquia_lift',
        'acquia_lift.message_box',
      ),
      array(
        'acquia_lift',
        'acquia_lift.dom_selector',
      ),
    ),
  );
  $libraries['acquia_lift.dom_selector'] = array(
    'title' => 'DOM selection library',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.dom_selector.js' => $options,
    ),
    'dependencies' => array(
      array(
        'acquia_lift',
        'qtip',
      ),
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
    ),
  );

  // Message box
  $libraries['acquia_lift.message_box'] = array(
    'title' => 'Assets to display a message box.',
    'website' => '',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.messagebox.js' => $options,
    ),
    'css' => array(
      $path . '/css/acquia_lift.messagebox.css' => array(),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
    ),
  );

  // Navbar module integration.
  $libraries['acquia_lift.navbar'] = array(
    'title' => 'Assets for integration with the Navbar module',
    'website' => 'http://drupal.org/project/navbar',
    'version' => '1.0',
    'js' => array(
      $path . '/js/acquia_lift.navbar.js' => $options,
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
    ),
  );

  // If navbar is enabled before the class names were changed from .menu then include the old css.
  if (acquia_lift_using_older_navbar()) {
    $libraries['acquia_lift.navbar']['css'][$path . '/css/acquia_lift.navbar_1-5.css'] = array(
      'weight' => 100,
    );
  }
  else {

    // Navbar is not enabled, or it is enabled with the later menu class names.
    $libraries['acquia_lift.navbar']['css'][$path . '/css/acquia_lift.navbar.css'] = array(
      'weight' => 100,
    );
  }

  // Save the menu class name to be used.
  $libraries['acquia_lift.navbar']['js'][] = array(
    'data' => array(
      'acquia_lift' => array(
        'menuClass' => acquia_lift_unibar_menu_class(),
      ),
    ),
    'type' => 'setting',
  );

  // Toolbar module integration.
  $libraries['acquia_lift.toolbar'] = array(
    'title' => 'Assets for integration with toolbar module',
    'website' => 'https://www.drupal.org/project/toolbar',
    'version' => '1.0',
    'js' => array(
      $path . '/js/acquia_lift.unified_navbar.toolbar.js' => array(),
    ),
    'dependencies' => array(
      array(
        'acquia_lift',
        'acquia_lift.unified.navbar',
      ),
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.debounce',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.displace',
      ),
    ),
  );

  // Admin_menu module integration.
  $libraries['acquia_lift.admin_menu'] = array(
    'title' => 'Assets for integration with admin_menu module',
    'website' => 'https://www.drupal.org/project/admin_menu',
    'version' => '1.0',
    'js' => array(
      $path . '/js/acquia_lift.unified_navbar.admin_menu.js' => array(),
    ),
    'dependencies' => array(
      array(
        'acquia_lift',
        'acquia_lift.unified.navbar',
      ),
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.debounce',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.displace',
      ),
    ),
  );

  // Unified navigation bar.
  $libraries['acquia_lift.unified.navbar'] = array(
    'title' => 'Unified navbar',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.unified_navbar.js' => array(),
      // The regular navbar file generates the drop-downs.
      $path . '/js/acquia_lift.navbar.js' => array(),
    ),
    'css' => array(
      $path . '/css/acquia_lift.unified_navbar.css',
      $path . '/css/navbar/navbar.module.css',
      $path . '/css/navbar/navbar.theme.css',
      $path . '/css/navbar/navbar.icons.css',
      $path . '/css/acquia_lift.navbar.css',
    ),
    'dependencies' => array(
      array(
        'acquia_lift',
        'modernizr',
      ),
      array(
        'system',
        'jquery',
      ),
      array(
        'acquia_lift',
        'underscore',
      ),
      array(
        'acquia_lift',
        'backbone',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.matchmedia',
      ),
      array(
        'system',
        'jquery.once',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.debounce',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.announce',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.displace',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.menu',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.tableheader',
      ),
    ),
  );

  // Set the menu class to be used.
  $libraries['acquia_lift.unified.navbar']['js'][] = array(
    'data' => array(
      'acquia_lift' => array(
        'menuClass' => acquia_lift_unibar_menu_class(),
      ),
    ),
    'type' => 'setting',
  );

  // Only load navbar.overlay if overlay is enabled.
  if (module_exists('overlay')) {
    $libraries['acquia_lift.unified.navbar']['dependencies'][] = array(
      'acquia_lift',
      'acquia_lift.navbar.overlay',
    );
  }

  // Reporting
  $libraries['acquia_lift.reports'] = array(
    'title' => 'Acquia Lift Reporting',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.reports.js' => array(),
      $path . '/js/acquia_lift.admin.js' => array(),
    ),
    'css' => array(
      $path . '/css/acquia_lift.reports.css',
      $path . '/css/acquia_lift.admin.css',
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'ui.slider',
      ),
      array(
        'acquia_lift',
        'd3',
      ),
      array(
        'acquia_lift',
        'rickshaw',
      ),
    ),
  );

  // Acquia Lift Targeting campaign management.
  $libraries['acquia_lift.targeting_admin'] = array(
    'title' => 'Acquia Lift Target administration',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift_target.admin.js' => array(),
      $path . '/js/acquia_lift.admin.js' => array(),
    ),
    'css' => array(
      $path . '/css/acquia_lift.admin.css',
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'ui.sortable',
      ),
      array(
        'system',
        'ui.draggable',
      ),
      array(
        'system',
        'ui.droppable',
      ),
      array(
        'acquia_lift',
        'acquia_lift.card',
      ),
    ),
  );

  // Help tooltips
  $libraries['acquia_lift.help'] = array(
    'title' => 'Acquia Lift Help Tooltips',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.help.js' => array(),
    ),
    'css' => array(
      $path . '/css/acquia_lift.help.css',
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'acquia_lift',
        'qtip',
      ),
    ),
  );

  // Card UI component
  $libraries['acquia_lift.card'] = array(
    'title' => 'Acquia Lift card component',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.card.js' => array(),
    ),
    'css' => array(
      $path . '/css/acquia_lift.card.css' => array(
        'group' => CSS_THEME,
      ),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
    ),
  );

  // Special input types.
  $libraries['acquia_lift.inputs'] = array(
    'title' => 'Acquia Lift inputs',
    'version' => VERSION,
    'js' => array(
      $path . '/js/acquia_lift.inputs.js' => array(),
    ),
    'css' => array(
      $path . '/css/acquia_lift.inputs.css',
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
    ),
  );

  /*** 3rd PARTY LIBRARY DEPENDENCIES ***/

  // Ensure that each 3rd party library dependency has a default variant.
  // Convert Libraries module data structures to library data structures.
  // Modernizr - required by Unified Navbar.
  $libraries['modernizr'] = _acquia_lift_convert_libraries_to_library(libraries_detect('modernizr'), array(
    'group' => JS_LIBRARY,
    'weight' => -100,
  ));

  // Underscore
  $libraries['underscore'] = _acquia_lift_convert_libraries_to_library(libraries_detect('underscore'), array(
    'group' => JS_LIBRARY,
    'weight' => -20,
  ));

  // Backbone
  $libraries['backbone'] = _acquia_lift_convert_libraries_to_library(libraries_detect('backbone'), array(
    'group' => JS_LIBRARY,
    'weight' => -19,
  ));

  // QTip
  $libraries['qtip'] = _acquia_lift_convert_libraries_to_library(libraries_detect('qtip'), array(
    'group' => JS_LIBRARY,
    'weight' => -15,
  ));

  // D3
  $libraries['d3'] = _acquia_lift_convert_libraries_to_library(libraries_detect('d3'), array(
    'group' => JS_LIBRARY,
    'weight' => -18,
  ));

  // Rickshaw
  $libraries['rickshaw'] = _acquia_lift_convert_libraries_to_library(libraries_detect('rickshaw'), array(
    'group' => JS_LIBRARY,
    'weight' => -15,
  ));

  /*** REQUIRED BY ACQUIA LIFT UNIFIED NAVBAR ***/

  // All libraries below this point are dependencies for the unified navbar.
  // These are identical to navbar dependencies.
  $libraries['acquia_lift.navbar.menu'] = array(
    'title' => 'Navbar nested accordion menus.',
    'version' => VERSION,
    'js' => array(
      $path . '/js/navbar/navbar.menu.js' => array(),
    ),
    'css' => array(
      $path . '/css/navbar/navbar.menu.css',
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'system',
        'jquery.once',
      ),
    ),
  );

  // Backport of D8 matchMedia polyfill.
  $libraries['acquia_lift.navbar.matchmedia'] = array(
    'title' => 'window.matchMedia polyfill',
    'website' => 'http://drupal.org/node/1815602',
    'version' => VERSION,
    'js' => array(
      $path . '/js/navbar/matchmedia.js' => array(),
    ),
  );

  // A utility function to limit calls to a function with a given time.
  $libraries['acquia_lift.navbar.debounce'] = array(
    'title' => 'Navbar debounce',
    'version' => VERSION,
    'js' => array(
      $path . '/js/navbar/debounce.js' => array(
        'group' => JS_LIBRARY,
      ),
    ),
  );

  // A utility function determine viewport offset distances.
  $libraries['acquia_lift.navbar.displace'] = array(
    'title' => 'Navbar displace',
    'version' => VERSION,
    'js' => array(
      $path . '/js/navbar/displace.js' => array(
        'group' => JS_LIBRARY,
      ),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.debounce',
      ),
    ),
  );

  // A utility for writing text to a common aria-live region.
  $libraries['acquia_lift.navbar.announce'] = array(
    'title' => 'Navbar announce',
    'version' => VERSION,
    'js' => array(
      $path . '/js/navbar/announce.js' => array(
        'group' => JS_LIBRARY,
      ),
    ),
    'dependencies' => array(
      array(
        'acquia_lift',
        'acquia_lift.navbar.debounce',
      ),
    ),
  );

  // Override Overlay methods to support displacement.
  $libraries['acquia_lift.navbar.overlay'] = array(
    'title' => 'Overlay method overrides to support D8 viewport displacement.',
    'version' => VERSION,
    'js' => array(
      // Load this file well after Overlay code has loaded.
      $path . '/js/navbar/navbar-overlay.js' => array(
        'weight' => 100,
      ),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.displace',
      ),
    ),
  );

  // Support Tableheader displacement.
  $libraries['acquia_lift.navbar.tableheader'] = array(
    'title' => 'Tableheader method to support D8 viewport displacement.',
    'version' => VERSION,
    'js' => array(
      // Load this file well after Overlay code has loaded.
      $path . '/js/navbar/navbar-tableheader.js' => array(
        'weight' => 100,
      ),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'acquia_lift',
        'acquia_lift.navbar.displace',
      ),
    ),
  );

  // The Acquia Lift debugger.
  $libraries['debugger'] = array(
    'title' => 'Acquia Lift debugger',
    'website' => '',
    'version' => VERSION,
    //@todo: the angular import should be done as a library dependency (similiar to backbone and underscore)
    'js' => array(
      $path . '/js/acquia_lift.debug.js' => $options,
      'https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.min.js' => $options,
      $path . '/js/acquia_lift.debugger.js' => $options,
      array(
        'data' => array(
          'acquia_lift' => array(
            'isDebugMode' => TRUE,
          ),
        ),
        'type' => 'setting',
      ),
    ),
    'css' => array(
      $path . '/css/acquia_lift.debugger.css' => array(),
    ),
    'dependencies' => array(
      array(
        'system',
        'jquery',
      ),
      array(
        'personalize',
        'storage',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_library_alter().
 *
 * Backport a couple of things from jQuery that are required.
 */
function acquia_lift_library_alter(&$libraries, $module) {
  $jquery_version =& drupal_static(__FUNCTION__, NULL);
  if ($module == 'system') {
    $jquery_version = $libraries['jquery']['version'];
  }
  if ($jquery_version && $module == 'acquia_lift') {

    // If the version of jQuery is old, we need to add `on` and `off`.
    if (version_compare($jquery_version, '1.7', '<') && isset($libraries['acquia_lift.page']['version'])) {
      $vaui_path = drupal_get_path('module', 'visitor_actions_ui');
      $libraries['acquia_lift.page']['js'][$vaui_path . '/js/jquery/ducktape.events.js'] = array(
        'group' => JS_LIBRARY,
      );
    }
  }

  // If we are using the unified navbar, then fix the behavior with overlay module.
  if ($module === 'overlay' && _acquia_lift_using_unified_navbar() && !empty($libraries)) {

    // Unset the child CSS file from Overlay and add our own.
    if (!empty($libraries['child']['css'])) {
      unset($libraries['child']['css']['modules/overlay/overlay-child.css']);
    }
    $libraries['child']['css'][drupal_get_path('module', 'acquia_lift') . '/css/navbar/navbar-overlay-child.css'] = array();
  }
}

/**
 * Implements hook_libraries_info().
 *
 * Takes the same approach as used in navbar project.
 *
 * @see Libraries module.
 */
function acquia_lift_libraries_info() {
  $libraries['modernizr'] = array(
    'name' => 'Modernizr',
    'vendor url' => 'https://github.com/Modernizr/Modernizr',
    'download url' => 'http://modernizr.com/download/#-inputtypes-svg-touch-cssclasses-addtest-teststyles-prefixes-elem_details',
    'version callback' => '_acquia_lift_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'modernizr.js',
          // @todo Document an actual example version string.
          'pattern' => '#[Mm]odernizr\\s+[Vv]?([0-9\\.]+)#',
        ),
        'minified' => array(
          'file' => 'modernizr-min.js',
          'pattern' => '#[Mm]odernizr\\s+[Vv]?([0-9\\.]+)#',
        ),
      ),
    ),
    'versions' => array(
      // Means ">=2.6.2": matches 2.6.2, 2.7.1, etc.
      '2.6.2' => array(
        'variants' => array(
          'source' => array(
            'files' => array(
              'js' => array(
                'modernizr.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'modernizr.js',
            ),
          ),
          'minified' => array(
            'files' => array(
              'js' => array(
                'modernizr-min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'modernizr-min.js',
            ),
          ),
        ),
      ),
    ),
  );
  $libraries['underscore'] = array(
    'name' => 'Underscore',
    'vendor url' => 'http://documentcloud.github.io/backbone/',
    'download url' => 'https://github.com/jashkenas/underscore/archive/1.5.2.zip',
    'version callback' => '_acquia_lift_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'underscore.js',
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
          // In the unminified Underscore.js 1.5.2, the version is defined on
          // line 68.
          'lines' => 100,
        ),
        'minified' => array(
          'file' => 'underscore-min.js',
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
          'cols' => 2000,
        ),
      ),
    ),
    'versions' => array(
      // Means ">=1.5.0": matches 1.5.0, 1.5.2, etc.
      '1.5.0' => array(
        'variants' => array(
          'source' => array(
            'files' => array(
              'js' => array(
                'underscore.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'underscore.js',
            ),
          ),
          'minified' => array(
            'files' => array(
              'js' => array(
                'underscore-min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'underscore-min.js',
            ),
          ),
        ),
      ),
    ),
  );
  $libraries['backbone'] = array(
    'name' => 'Backbone',
    'vendor url' => 'http://documentcloud.github.io/backbone/',
    'download url' => 'https://github.com/jashkenas/backbone/archive/1.1.0.zip',
    'version callback' => '_acquia_lift_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'backbone.js',
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
          // In the unminified Backbone.js 1.1.0, the version is defined on line
          // 38.
          'lines' => 50,
        ),
        'minified' => array(
          'file' => 'backbone-min.js',
          'pattern' => '#VERSION *\\W *[\'\\"]{1}(.*?)[\'\\"]{1}#',
        ),
      ),
    ),
    'versions' => array(
      // Means ">=1.0.0": matches 1.0.0, 1.1.0, etc.
      '1.0.0' => array(
        'variants' => array(
          'source' => array(
            'name' => 'Backbone',
            'files' => array(
              'js' => array(
                'backbone.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'backbone.js',
            ),
            'dependencies' => array(
              'underscore (>=1.5.0)',
            ),
          ),
          'minified' => array(
            'name' => 'Backbone',
            'files' => array(
              'js' => array(
                'backbone-min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'backbone-min.js',
            ),
            'dependencies' => array(
              'underscore (>=1.5.0)',
            ),
          ),
        ),
      ),
    ),
  );
  $libraries['chosen'] = array(
    'name' => 'Chosen',
    'vendor url' => 'http://harvesthq.github.io/chosen/',
    'download url' => 'https://github.com/harvesthq/chosen/releases/download/v1.1.0/chosen_v1.1.0.zip',
    'version callback' => '_acquia_lift_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'chosen.jquery.js',
          'pattern' => '/Version (1\\.\\d\\.\\d)+/',
        ),
        'minified' => array(
          'file' => 'chosen.jquery.min.js',
          'pattern' => '/v(1\\.\\d\\.\\d)+/',
        ),
      ),
    ),
    'versions' => array(
      '1.1.0' => array(
        'variants' => array(
          'source' => array(
            'files' => array(
              'js' => array(
                'chosen.jquery.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'chosen.jquery.js',
            ),
          ),
          'minified' => array(
            'files' => array(
              'js' => array(
                'chosen.jquery.min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'chosen.jquery.min.js',
            ),
          ),
        ),
      ),
    ),
  );

  // @todo version detection assumes a version pattern within the same filename
  // but QTip has version-specific filenames and this needs to be better
  // addressed.
  $libraries['qtip'] = array(
    'name' => 'QTip',
    'vendor url' => 'http://craigsworks.com/projects/qtip/',
    'download url' => 'http://craigsworks.com/projects/qtip/download/',
    'version callback' => '_acquia_lift_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'jquery.qtip-1.0.0-rc3.js',
          'pattern' => '/Version : (1\\.\\d\\.\\d)+/',
        ),
        'minified' => array(
          'file' => 'jquery.qtip-1.0.0-rc3.min.js',
          'pattern' => '/Version : (1\\.\\d\\.\\d)+/',
        ),
      ),
    ),
    'versions' => array(
      '1.0.0' => array(
        'variants' => array(
          'source' => array(
            'files' => array(
              'js' => array(
                'jquery.qtip-1.0.0-rc3.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'jquery.qtip-1.0.0-rc3.js',
            ),
          ),
          'minified' => array(
            'files' => array(
              'js' => array(
                'jquery.qtip-1.0.0-rc3.min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'jquery.qtip-1.0.0-rc3.min.js',
            ),
          ),
        ),
      ),
    ),
  );

  // NOTE: Rickshaw does not declare a version.
  $libraries['rickshaw'] = array(
    'name' => 'Rickshaw',
    'vendor url' => 'http://code.shutterstock.com/rickshaw/',
    'download url' => 'https://github.com/shutterstock/rickshaw',
    'version callback' => '_acquia_lift_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'rickshaw.js',
          'pattern' => '/(Rickshaw)/',
        ),
        'minified' => array(
          'file' => 'rickshaw.min.js',
          'pattern' => '/(Rickshaw)/',
        ),
      ),
    ),
    'versions' => array(
      // Rickshaw does not define versions but this will at least test to see
      // if it is installed.
      'Rickshaw' => array(
        'variants' => array(
          'source' => array(
            'name' => 'Rickshaw',
            'files' => array(
              'js' => array(
                'rickshaw.js',
              ),
              'css' => array(
                'rickshaw.css',
              ),
            ),
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'rickshaw.js',
            ),
            'dependencies' => array(
              'd3 (>=3.3.6)',
            ),
          ),
          'minified' => array(
            'name' => 'Rickshaw',
            'files' => array(
              'js' => array(
                'rickshaw.min.js',
              ),
              'css' => array(
                'rickshaw.min.css',
              ),
            ),
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'rickshaw.min.js',
            ),
            'dependencies' => array(
              'd3 (>=3.3.6)',
            ),
          ),
        ),
      ),
    ),
  );
  $libraries['d3'] = array(
    'name' => 'D3',
    'vendor url' => 'http://d3js.org',
    'download url' => 'https://github.com/mbostock/d3',
    'version callback' => '_acquia_lift_libraries_get_version',
    'variant order' => array(
      'minified',
      'source',
    ),
    'version arguments' => array(
      'variants' => array(
        'source' => array(
          'file' => 'd3.js',
          'pattern' => '/version: "(\\d\\.\\d{1,2}\\.\\d{0,3})"/',
        ),
        'minified' => array(
          'file' => 'd3.min.js',
          'pattern' => '/{version:"(\\d\\.\\d{1,2}\\.\\d{0,3})"}/',
        ),
      ),
    ),
    'versions' => array(
      // Means ">=1.0.0": matches 1.0.0, 1.1.0, etc.
      '3.3.6' => array(
        'variants' => array(
          'source' => array(
            'name' => 'D3',
            'files' => array(
              'js' => array(
                'd3.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to not be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'd3.js',
            ),
          ),
          'minified' => array(
            'name' => 'D3',
            'files' => array(
              'js' => array(
                'd3.min.js',
              ),
            ),
            // Without a variant callback, the variant is assumed to not be
            // installed.
            'variant callback' => '_acquia_lift_libraries_variant_exists',
            'variant arguments' => array(
              'd3.min.js',
            ),
          ),
        ),
      ),
    ),
  );
  return $libraries;
}

/**
 * Determines the version of a library.
 *
 * This is used in case different variants of the library are shipped separately
 * and, thus, different variants can contain different versions.
 *
 * @param array $library
 *   An associative array containing all information about the library. The
 *   library is assumed to have the following non-standard keys:
 *   - variant order: An array of variant names, ordered from the most preferred
 *     variant to the least preferred.
 * @param array $options
 *   An associative array with the following keys:
 *   - variants: An array of options for libraries_get_version() keyed by
 *     variant name.
 *
 */
function _acquia_lift_libraries_get_version(&$library, $options = array()) {
  $versions = array();
  foreach ($library['variant order'] as $variant_name) {
    $variant = $library['version arguments']['variants'][$variant_name];

    // Use the libraries get version function to determine the version string.
    $versions[$variant_name] = libraries_get_version($library, $variant);
  }

  // If no versions could be found for any of the variant, there is no version
  // to return. If different versions have been found, there is no way to
  // determine the correct one. We cannot use the information on the preferred
  // variants because we cannot guarantee that a less preferred variant will not
  // be loaded. Null values are fine. Either that variant file doesn't exist
  // or id doesn't contain version information. As long as the there is no
  // conflicting version information, the check should pass.
  $versions = array_filter($versions, '_acquia_lift_libraries_filter_null_values');
  $version = array_unique($versions);
  $vcount = count($version);
  if ($vcount == 1) {

    // A version number exists, so suppress any errors that any individual
    // variant might have raised.
    unset($library['error']);
    unset($library['error message']);
    return array_shift($version);
  }
  elseif ($vcount > 1) {
    $output = array();
    foreach ($versions as $name => $v) {
      $output[] = t('@name (@v)', array(
        '@name' => $name,
        '@v' => $v,
      ));
    }
    $library['error'] = 'inconsistent versions';
    $library['error message'] = t('The library\'s variants returned inconsistent versions: @variant_info', array(
      '@variant_info' => implode(', ', $output),
    ));
  }

  // If the version count is zero, then let the error from libraries_get_version
  // propagate through.
}

/**
 * Determines if an item is empty or not.
 *
 * @param string $item
 *   A version number string.
 * @return boolean
 *   Whether the $item's value is empty or not.
 */
function _acquia_lift_libraries_filter_null_values($item) {
  return !empty($item);
}

/**
 * Converts a libraries module array to a hook_library array.
 *
 * @todo Libraries API should automatically register all libraries in
 *   hook_library(). See https://drupal.org/node/1386368
 *
 * @return Array
 *  Returns a standard Drupal library definition structure.
 */
function _acquia_lift_convert_libraries_to_library($library, $options = array()) {

  // If the library wasn't installed, don't bother converting it.
  if (!$library['installed']) {
    return array();
  }
  $converted = array();
  $files = array();

  // Get the library files from one of the installed variants.
  if ($name = _acquia_lift_libraries_get_preferred_variant_name($library)) {
    $files = $library['variants'][$name]['files'];
  }

  // Define the library if files exist for it.
  if (!empty($files)) {

    // This is the basic structure expected by hook_library().
    $converted = array(
      'title' => $library['name'],
      'website' => $library['vendor url'],
      'version' => $library['version'],
    );
    foreach ($files as $type => $paths) {
      foreach ($paths as $filename => $data) {
        $converted[$type][$library['library path'] . '/' . $filename] = $options;
      }
    }
  }
  return $converted;
}

/**
 * Libraries API variant callback.
 */
function _acquia_lift_libraries_variant_exists($library, $variant_name, $required_file) {
  return file_exists($library['library path'] . '/' . $required_file);
}

/**
 * Returns the variant that should be loaded based on order preference.
 *
 * @param array $library
 *   A libraries module library definition array.
 * @return string
 *   The name of the variant that should be loaded.
 */
function _acquia_lift_libraries_get_preferred_variant_name($library) {
  if (!empty($library['variant order'])) {
    foreach ($library['variant order'] as $name) {
      if ($variant = $library['variants'][$name]) {
        if ($variant['installed']) {
          return $name;
        }
      }
    }
  }
  return NULL;
}

/**
 * Helper function to display a message when a missing library is detected.
 *
 * This can provide targeted messaging on pages that require a JavaScript
 * library in order to be useful.
 *
 * @param $required_libraries
 *   An array of required library names based on the names used in the
 *   hook_libraries_info method.
 * @param $missing_message
 *   The basic message to print before a listing of missing libraries and their
 *   download links.
 * @return bool
 *   True if a library was missing, false if no missing libraries.
 */
function _acquia_lift_missing_library_warning($required_libraries, $missing_message) {
  $libraries = module_invoke('acquia_lift', 'libraries_info');
  $missing = array();
  foreach ($required_libraries as $required) {
    $detected = libraries_detect($required);
    if (empty($detected['installed'])) {
      $missing[] = array(
        'name' => $libraries[$required]['name'],
        'download_url' => $libraries[$required]['download url'],
      );
    }
    else {
      if (!empty($detected['error'])) {
        drupal_set_message($detected['error message'], 'error');
      }
    }
  }
  if (!empty($missing)) {
    $items = array();
    foreach ($missing as $lib) {
      $items[] = t('%libname - <a href="!download_url" target="_blank">download</a>', array(
        '%libname' => $lib['name'],
        '!download_url' => $lib['download_url'],
      ));
    }
    drupal_set_message(theme('item_list', array(
      'type' => 'ul',
      'title' => $missing_message,
      'items' => $items,
    )), 'error');
    return TRUE;
  }
  return FALSE;
}

/**
 * Helper function to determine if the site is using the Acquia Lift unified
 * navigation bar.
 *
 * @return bool
 *   True if using a module that is supported by the unified navigation bar.
 */
function _acquia_lift_using_unified_navbar() {
  $supported_modules = array(
    'admin_menu',
    'toolbar',
  );
  foreach ($supported_modules as $module_name) {
    if (module_exists($module_name)) {
      return TRUE;
    }
  }
}

/**
 * Helper function to determine if an older version of navbar is in use.
 *
 * The main difference to be dealt with here is that in navbar <= 1.5 the
 * main menu class used is 'menu' while later versions standardized to
 * 'navbar-menu' to avoid conflicts with the standard menu system.
 */
function acquia_lift_using_older_navbar() {

  // The menu tree preprocessor was added after navbar 1.5 in order to handle applying
  // the updated navbar-menu class.
  return module_exists('navbar') && !function_exists('template_preprocess_navbar_menu_tree');
}

/**
 * Helper function to determine the menu class name to use for unibar structure.
 */
function acquia_lift_unibar_menu_class() {
  return acquia_lift_using_older_navbar() ? 'menu' : 'navbar-menu';
}

/**
 * Create and attach the assets for Acquia Lift navigation to an element on the
 * page.
 *
 * @param $element
 *   The element to attach assets to.
 * @param $hide
 *   Indicates if the navigation should be hidden by default.
 */
function _acquia_lift_navigation_attach_assets(&$element, $hide = FALSE) {
  $element['acquia_lift']['#access'] = user_access('manage personalized content');
  $element['acquia_lift']['#attached']['library'][] = array(
    'acquia_lift',
    'acquia_lift.personalize',
  );
  $element['acquia_lift']['#attached']['library'][] = array(
    'acquia_lift',
    'acquia_lift.message_box',
  );
  if (_acquia_lift_using_unified_navbar()) {
    $element['acquia_lift']['nav']['#type'] = 'acquia_lift_navbar';

    // Make the Acquia Lift tool bar show up after other content.
    $element['acquia_lift']['#weight'] = 100;
    $element['#sorted'] = FALSE;

    // Specific module integration libraries.
    if (module_exists('admin_menu')) {
      $element['acquia_lift']['#attached']['library'][] = array(
        'acquia_lift',
        'acquia_lift.admin_menu',
      );
    }
    else {
      if (module_exists('toolbar')) {
        $element['acquia_lift']['#attached']['library'][] = array(
          'acquia_lift',
          'acquia_lift.toolbar',
        );
      }
    }
    $element['acquia_lift']['#attached']['js'][] = array(
      'data' => array(
        'acquia_lift' => array(
          'hideTrayDefault' => $hide,
        ),
      ),
      'type' => 'setting',
    );
  }
}

/**
 * Pre-render function for Acquia Lift unified navbar element.
 */
function acquia_lift_navbar_pre_render($element) {
  module_load_include('inc', 'acquia_lift', 'acquia_lift.ui');
  return acquia_lift_navbar_ui_pre_render($element);
}

/**
 * Pre-render function for an item within the unified navbar element.
 */
function acquia_lift_navbar_pre_render_item($element) {
  module_load_include('inc', 'acquia_lift', 'acquia_lift.ui');
  return acquia_lift_navbar_ui_pre_render_item($element);
}

/**
 * Implements hook_system_info_alter().
 *
 * Indicate that the 'page_top' region (in which the navbar will be displayed)
 * is an overlay supplemental region that should be refreshed whenever its
 * content is updated.
 *
 * This information is provided for any module that might need to use it, not
 * just the core Overlay module.
 *
 * @see navbar_system_info_alter().
 */
function acquia_lift_system_info_alter(&$info, $file, $type) {
  if ($type == 'theme' && _acquia_lift_using_unified_navbar()) {
    $info['overlay_supplemental_regions'][] = 'page_top';
  }
}

/**
 * Implements hook_js_alter().
 *
 * @see navbar_js_alter().
 */
function acquia_lift_js_alter(&$javascript) {

  // Only load the tableheader offset script if the core tableheader script
  // is loaded and acquia lift is loaded.
  if (isset($javascript['misc/tableheader.js']) && _acquia_lift_using_unified_navbar() && isset($javascript[drupal_get_path('module', 'acquia_lift') . '/js/acquia_lift.navbar.js'])) {
    drupal_add_js(array(
      'tableHeaderOffset' => 'Drupal.navbar.height',
    ), array(
      'type' => 'setting',
    ));
  }
}

/**
 * Implements hook_modernizr_info().
 */
function acquia_lift_modernizr_info() {
  $tests = array();

  // Feature tests
  $tests[] = 'inputtypes';
  $tests[] = 'svg';
  $tests[] = 'touch';
  $tests[] = 'elem_details';

  // Extensibility
  $tests[] = 'addtest';
  $tests[] = 'teststyles';
  $tests[] = 'prefixes';
  return $tests;
}

/**
 * Implements hook_navbar().
 */
function acquia_lift_navbar() {
  $items = array();
  $menu = menu_tree_all_data('acquia-lift-controls');
  $usingUnifiedNavbar = _acquia_lift_using_unified_navbar();
  if (personalize_debug_mode_enabled()) {
    $debugger_running = acquia_lift_debug_mode_enabled();
    $debugger_link = current_path();
    $link_title = $debugger_running ? t('End Acquia Lift Inspector session') : t('Start Acquia Lift Inspector');

    // Add in a link to enable/disable the debugger.
    $menu[] = array(
      'link' => array(
        'mlid' => 'acquia_lift_debugger_id',
        'link_title' => $link_title,
        'title' => $link_title,
        'menu_name' => 'acquia-lift-controls',
        'link_path' => $debugger_link,
        'href' => $debugger_link,
        'has_children' => '0',
        'expanded' => '1',
        'depth' => '1',
        'weight' => '50',
        'hidden' => '0',
        'access' => '1',
        'in_active_trail' => FALSE,
        'localized_options' => array(
          'attributes' => array(
            'class' => array(
              'visitor-actions-ui-ignore',
              'acquia-lift-menu-link',
              'acquia-lift-debugger',
              'acquia-lift-navbar-secondary',
            ),
            'data-acquia-lift-debugger-running' => $debugger_running ? 'true' : 'false',
          ),
        ),
      ),
      'below' => array(),
    );
  }
  if (!empty($menu)) {
    $items['acquia_lift'] = array(
      '#access' => user_access('manage personalized content'),
      '#type' => $usingUnifiedNavbar ? 'acquia_lift_navbar_item' : 'navbar_item',
      'tray' => array(
        '#heading' => t('Personalization controls'),
        'navbar_personalization' => array(
          '#type' => 'container',
          '#attributes' => array(
            'class' => array(
              'navbar-menu-acquia-lift-controls',
              'acquia-lift-controls',
            ),
          ),
          'personalization' => menu_tree_output($menu),
        ),
        '#wrapper_attributes' => array(
          'class' => array(
            'navbar-tray-acquia-lift',
          ),
        ),
      ),
      '#weight' => 20,
    );
    if (!$usingUnifiedNavbar) {

      // Add the tab trigger for the tray.
      $items['acquia_lift']['tab'] = array(
        '#type' => 'link',
        '#title' => t('Acquia Lift'),
        '#href' => 'admin/structure/personalize',
        '#options' => array(
          'attributes' => array(
            'title' => t('Personalization settings'),
            // @todo, the .navbar-tab class is provided here because Demo
            // Framework is on an older version of Navbar (dd542e1). Once
            // DF is updated to the latest Navbar release, the .navbar-tab
            // class can be removed here. The class should be added in
            // template_preprocess_navbar_tab_wrapper, which only add a class
            // .tab in older versions.
            'class' => array(
              'navbar-icon',
              'navbar-icon-acquia-lift',
              'navbar-tab',
            ),
          ),
        ),
      );

      // Add the library for the navbar integration.
      $items['acquia_lift']['#attached'] = array(
        'library' => array(
          array(
            'acquia_lift',
            'acquia_lift.navbar',
          ),
        ),
      );
    }
  }
  return $items;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function acquia_lift_form_personalize_campaign_wizard_alter(&$form, &$form_state, $form_id) {
  if ($form_state['storage']['step'] == 'results') {
    unset($form['actions']);
  }

  // Add in the basic functionality.
  $agent = $form['#agent'];
  if (!acquia_lift_is_configured()) {
    drupal_set_message(t('Your Acquia Lift account info has not been configured. Acquia Lift personalizations can not be created until you configure your account info !here', array(
      '!here' => l('here', 'admin/config/content/personalize/acquia_lift'),
    )), 'error');
    $form['agent_basic_info']['create']['#disabled'] = TRUE;
  }

  // Stylize the create campaign button for new campaigns.
  $form['agent_basic_info']['create']['#attributes']['class'][] = 'acquia-lift-submit-button';
  if (empty($agent->machine_name)) {
    $form['agent_basic_info']['agent_type'] = array(
      '#type' => 'value',
      '#value' => 'acquia_lift_target',
    );
    $form['agent_basic_info']['title']['#attributes']['placeholder'] = t('Example: Q3 Summer Promotions');

    // All other modifications only apply to the edit form.
    return;
  }

  // Only reports can be shown for legacy campaigns.
  $legacy_campaigns = variable_get('acquia_lift_legacy_agents', array());
  if (in_array($agent->machine_name, $legacy_campaigns)) {
    module_load_include('inc', 'personalize', 'personalize_admin');
    $form = drupal_get_form('acquia_lift_report', $agent);
    return;
  }
  if (!acquia_lift_is_targeting_agent($agent)) {
    return;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin.wizard');
  acquia_lift_personalize_campaign_wizard_alter($form, $form_state, $agent);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters the option sets form so that it can't be used for explicit targeting
 * on a Lift Target agent.
 */
function acquia_lift_form_personalize_agent_option_sets_form_alter(&$form, &$form_state) {
  if (!acquia_lift_is_targeting_agent($form['#agent'])) {
    return;
  }
  foreach ($form['variations']['primary']['option_sets'] as $name => $elements) {
    if (isset($elements['options'])) {
      unset($form['variations']['primary']['option_sets'][$name]['options']);
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters the status change form so that:
 *   1) If the personalization is already started, render the buttons;
 *      otherwise link to the review page.
 *   2) Legacy campaigns whose status is completed cannot be changed to any other status.
 */
function acquia_lift_form_personalize_status_change_form_alter(&$form, &$form_state) {
  $agent_name = $form['agent_name']['#value'];
  $agent = personalize_agent_load($agent_name);
  $status = personalize_agent_get_status($agent_name);

  // If the personalization is already started, render the buttons; otherwise link to the review page.
  if (acquia_lift_is_targeting_agent($agent) && ($status === PERSONALIZE_STATUS_NOT_STARTED || $status === PERSONALIZE_STATUS_PAUSED)) {
    $form['status_wrapper'] = array(
      '#type' => 'markup',
      '#markup' => l(t('Review and start'), 'admin/structure/personalize/manage/' . $agent_name . '/review'),
    );
  }

  // Legacy campaigns whose status is completed cannot be changed to any other status.
  $legacy_campaigns = variable_get('acquia_lift_legacy_agents', array());
  if (in_array($agent_name, $legacy_campaigns) && $status == PERSONALIZE_STATUS_COMPLETED) {
    $form['status_wrapper'] = array(
      '#type' => 'markup',
      '#markup' => t('No status change allowed.'),
    );
  }
}

/**
 *  Implements hook_personalize_campaign_action_links_alter().
 */
function acquia_lift_personalize_campaign_action_links_alter(&$links, $agent_data, $destination) {
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  if (acquia_lift_target_definition_changes_allowed($agent_data)) {

    // If this agent has nested test agents, then provide a link to reset data.
    $nested = acquia_lift_get_nested_tests($agent_data);
    if (empty($nested)) {
      return;
    }
  }
  else {

    // If no changes are allowed, then don't provide any actions except an
    // action to 'pause' the campaign.
    $status = personalize_agent_get_status($agent_data->machine_name);
    switch ($status) {
      case PERSONALIZE_STATUS_RUNNING:
        $message = t('Personalizations that are running cannot be edited. Click "Pause" to allow it to be edited. Personalizations that are paused display the default variations to visitors.');
        $button_text = t("Pause");
        $next_status = PERSONALIZE_STATUS_PAUSED;
        break;
      case PERSONALIZE_STATUS_SCHEDULED:
        $message = t('Personalizations with scheduled start dates cannot be edited.  Click "Make editable" to allow it to be edited. After you have made your changes, go to the Review section to restart the personalization.');
        $button_text = t('Make editable');
        $next_status = empty($agent_data->started) ? PERSONALIZE_STATUS_NOT_STARTED : PERSONALIZE_STATUS_PAUSED;
        break;
      case PERSONALIZE_STATUS_COMPLETED:
        $message = t('Archived personalizations cannot be edited.  Click "Unarchive" for the personalization to restore it to a Paused status, allowing it to be edited.');
        $button_text = t('Unarchive');
        $next_status = PERSONALIZE_STATUS_PAUSED;
        break;
      default:

        // Should not get here.
        return;
    }
    drupal_set_message($message, 'warning', FALSE);
    $links = array(
      array(
        'title' => $button_text,
        'href' => '#',
        'attributes' => array(
          'data-personalize-action' => 'personalize-wizard-status-' . $next_status,
        ),
      ),
    );
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function acquia_lift_form_personalize_admin_form_alter(&$form, &$form_state) {
  module_load_include('inc', 'acquia_lift', 'acquia_lift.ui');
  acquia_lift_chosenify_element($form['personalize_visitor_context_disabled']);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function acquia_lift_form_personalize_elements_form_alter(&$form, &$form_state) {
  if (empty($form['agent_select']['#value'])) {
    return;
  }
  $agent_data = personalize_agent_load($form['agent_select']['#value']);
  if ($agent_data->plugin !== 'acquia_lift_target') {
    return;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  if (!acquia_lift_target_definition_changes_allowed($agent_data)) {
    drupal_set_message(t('Variations that are in use cannot be removed until the %campaign_name personalization is paused.', array(
      '%campaign_name' => $agent_data->label,
    )));
  }
  $form['#validate'][] = 'acquia_lift_personalize_elements_form_validate';
}

/**
 * Validation handler for personalize_elements_form.
 */
function acquia_lift_personalize_elements_form_validate(&$form, &$form_state) {
  if (!isset($form_state['values']['agent_select'])) {
    return;
  }
  $agent = $form_state['values']['agent_select'];
  $agent_data = personalize_agent_load($agent);
  if ($agent_data->plugin !== 'acquia_lift_target') {
    return;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  if (acquia_lift_target_definition_changes_allowed($agent_data)) {
    return;
  }

  // Variations cannot be removed from running acquia_lift_target campaigns if
  // they are used in targeting.
  foreach ($form_state['values']['options'] as $delta => $option) {
    if (empty($option['personalize_elements_content']) && !empty($option['option_id'])) {
      if (acquia_lift_target_option_targeted($agent, $option['option_id'])) {
        form_set_error('options][' . $delta . '][personalize_elements_content', t('Variations cannot be removed until the %campaign_name personalization is paused.', array(
          '%campaign_name' => $agent_data->label,
        )));
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function acquia_lift_form_personalize_elements_configuration_form_alter(&$form, &$form_state) {
  $form['acquia_lift_html_context_strip'] = array(
    '#type' => 'checkbox',
    '#title' => t('Filter HTML when creating variations'),
    '#description' => t('Select this check box to remove script tags and inline styles from the Edit HTML link when adding variations using the Acquia Lift menu bar. Clear the check box to display unfiltered HTML for variations.'),
    '#default_value' => variable_get('acquia_lift_html_context_strip', 1),
  );
  $form['#submit'][] = 'acquia_lift_personalize_elements_confirmation_form_submit';
}

/**
 * Submit handler for altered personalize_elements_configuration_form.
 */
function acquia_lift_personalize_elements_confirmation_form_submit($form, &$form_state) {
  variable_set('acquia_lift_html_context_strip', $form_state['values']['acquia_lift_html_context_strip']);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function acquia_lift_form_personalize_blocks_form_alter(&$form, &$form_state) {
  $form['#submit'][] = 'acquia_lift_personalize_blocks_form_submit';
  if (!isset($form['agent_select']['#value'])) {
    return;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  $agent = $form['agent_select']['#value'];
  $agent_data = personalize_agent_load($agent);
  if (!acquia_lift_target_definition_changes_allowed($agent_data)) {
    $show_remove_message = FALSE;
    foreach (element_children($form['pblock_wrapper']['blocks']) as $delta) {
      $option_id = $form['pblock_wrapper']['blocks'][$delta]['option_id']['#value'];
      if (acquia_lift_target_option_targeted($agent, $option_id)) {
        $form['pblock_wrapper']['blocks'][$delta]['remove']['#access'] = FALSE;
        $show_remove_message = TRUE;
      }
    }
    if ($show_remove_message) {
      drupal_set_message(t('Variations in use cannot be removed from %variation_set until the %agent_name personalization is paused.', array(
        '%variation_set' => $form['title']['#default_value'],
        '%agent_name' => $agent_data->label,
      )), 'warning');
    }
  }
}

/**
 * Submit handler for altered personalize_elements_form.
 */
function acquia_lift_personalize_blocks_form_submit($form, &$form_state) {
  $redirect = $form_state['redirect'];
  if ($redirect === FALSE) {
    return;
  }

  // If there is a destination specified, then it should take priority over the
  // existing redirect value (which is what happens in Drupal form handling).
  // Check this manually rather than using drupal_get_destination() as it
  // returns the current URL if the destination is not yet.
  $destination = drupal_get_destination();
  if (isset($_GET['destination'])) {
    $redirect = $destination['destination'];

    // Now that it has been handled, remove it so taht it doesn't overwrite
    // our own redirect location.
    unset($_GET['destination']);
  }
  else {
    if (empty($redirect)) {

      // If there is no destination and no redirect, then use the default
      // Drupal destination of the current path.
      $redirect = $destination['redirect'];
    }
  }

  // Convert the existing redirect into a redirect that accepts query parameters.
  if (is_string($redirect)) {
    $redirect = array(
      $redirect,
      array(
        'query' => array(),
      ),
    );
  }

  // Add a message to be shown.
  $redirect[1]['query']['liftpm'] = 'new_block|' . $form_state['values']['title'];
  $form_state['redirect'] = $redirect;
}

/**
 * Implements hook_personalize_fields_form_element_alter().
 */
function acquia_lift_personalize_fields_form_element_alter(&$element, $lang) {
  $element['#attributes']['class'][] = 'personalizable-field';
  if (isset($element[$lang]['#theme']) && $element[$lang]['#theme'] == 'field_multiple_value_form') {
    $element[$lang]['#theme'] = 'acquia_lift_personalizable_field_form';
  }
  $element['#attached']['library'][] = array(
    'acquia_lift',
    'acquia_lift.fields',
  );
  $element['#element_validate'][] = 'acquia_lift_personalize_fields_form_element_validate';
}

/**
 * Personalized fields element validation.
 */
function acquia_lift_personalize_fields_form_element_validate($element, &$form_state, $form) {
  $agent = isset($element['#attributes']['data-personalize-agent']) ? $element['#attributes']['data-personalize-agent'] : NULL;
  if (empty($agent)) {
    return;
  }
  $agent_data = personalize_agent_load($agent);
  if ($agent_data->plugin !== 'acquia_lift_target') {
    return;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  $changes_allowed = acquia_lift_target_definition_changes_allowed($agent_data);
  if ($changes_allowed) {
    return;
  }

  // We can tell if a text field is emptied, but not if a remove button has been
  // pressed because that removes the personalize_fields_option_id value. We
  // show a specific field error when we can, otherwise, check the number of
  // options and show for the entire field.
  $lang_element = $element[$element['#language']];
  $field_options = element_children($lang_element);
  $field_title = isset($lang_element['#title']) ? $lang_element['#title'] : $lang_element['#field_name'];
  $number_options = 0;
  $shown_error = FALSE;
  foreach ($field_options as $index) {
    if (!is_numeric($index)) {
      continue;
    }
    $option_field = drupal_array_get_nested_value($form_state['values'], $lang_element[$index]['#parents']);
    $option_value = personalize_fields_get_option_value($option_field);
    if (!empty($option_value)) {

      // Looking specifically for options that have been deleted.
      $number_options++;
      continue;
    }

    // Determine if the deleted option is targeted in a running campaign
    // if it is possible to do so.
    if (!empty($lang_element[$index]['personalize_fields_option_id'])) {
      $option_id = $lang_element[$index]['personalize_fields_option_id']['#value'];

      // A field cannot be deleted while it is being used in targeting.
      if (acquia_lift_target_option_targeted($agent_data->machine_name, $option_id)) {
        $shown_error = TRUE;
        form_set_error(implode('][', $lang_element[$index]['#parents']), t('The %field_title field cannot be removed until the personalization is paused.', array(
          '%field_title' => $field_title,
        )));
      }
    }
  }

  // If an error was not found based on option values, then check the number of
  // options for this element against the number saved in the option set.
  if (!$shown_error) {
    $option_set = personalize_option_set_load($element['#attributes']['data-personalize-osid']);
    if ($number_options < count($option_set->options)) {
      form_set_error(implode('][', $element['#parents']), t('Fields for %field_title in use by personalization cannot be removed until the personalization is paused.', array(
        '%field_title' => $field_title,
      )));
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function acquia_lift_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  if (!empty($form['field']['settings']['personalize']['enabled'])) {
    $field = $form['#field'];

    // Due to the way the Acquia Lift JS widget works for personalized fields,
    // we actually have to enforce (for now) that when Acquia Lift is enabled,
    // only fields with unlimited cardinality may be personalized.
    $form['field']['settings']['personalize']['enabled']['#states'] = array(
      'enabled' => array(
        ':input[name="field[cardinality]"]' => array(
          'value' => -1,
        ),
      ),
      'valid' => array(
        ':input[name="field[cardinality]"]' => array(
          'value' => -1,
        ),
      ),
    );
    $form['field']['settings']['personalize']['enabled']['#description'] = t('This setting is only valid for fields with unlimited cardinality.');

    // Add a state to the form elements to make sure they only appear when the
    // personalizable checkbox is checked.
    $state = array(
      ':input[name="field[settings][personalize][enabled]"]' => array(
        'checked' => TRUE,
      ),
    );

    //  Make sure only acquia_lift_target shows up as an agent type for lift
    // agents.
    unset($form['field']['settings']['personalize']['agent_type']['#options']['acquia_lift']);

    // Checkbox for auto creation of a goal should only be visible if the selected
    // campaign type is one of the Lift campaign types.
    $visible_states = array(
      array(
        'value' => 'acquia_lift_target',
      ),
    );
    $form['field']['settings']['personalize']['create_goal'] = array(
      '#type' => 'checkbox',
      '#title' => t('Auto-create goal'),
      '#default_value' => isset($field['settings']['personalize']['create_goal']) ? $field['settings']['personalize']['create_goal'] : 1,
      '#description' => t('Should a goal of "clicks the field" automatically get created for this personalization?'),
      '#states' => array(
        'visible' => array(
          ':input[name="field[settings][personalize][agent_type]"]' => $visible_states,
        ),
      ),
    );
    personalize_form_element_add_states($state, $form['field']['settings']['personalize']['create_goal']);

    // Textarea to limit the pages the goal fires on.
    $form['field']['settings']['personalize']['goal_pages'] = array(
      '#type' => 'textarea',
      '#title' => t('Pages for goal'),
      '#default_value' => isset($field['settings']['personalize']['goal_pages']) ? $field['settings']['personalize']['goal_pages'] : '',
      '#description' => t('Specify pages to limit the goal to using their paths (one path per line.) Leave blank to apply to all pages.'),
      '#states' => array(
        'visible' => array(
          ':input[name="field[settings][personalize][agent_type]"]' => $visible_states,
          ':input[name="field[settings][personalize][create_goal]"]' => array(
            'checked' => TRUE,
          ),
        ),
      ),
    );
    personalize_form_element_add_states($state, $form['field']['settings']['personalize']['goal_pages']);
  }
}

/**
 * Implements hook_module_implements_alter().
 */
function acquia_lift_module_implements_alter(&$implementations, $hook) {
  if (in_array($hook, array(
    'form_alter',
    'form_field_ui_field_edit_form_alter',
  )) && isset($implementations['acquia_lift'])) {

    // Ensure that our hook_form_field_ui_field_edit_form_alter implementation
    // runs last (after personalize_fields.module).
    $group = $implementations['acquia_lift'];
    unset($implementations['acquia_lift']);
    $implementations['acquia_lift'] = $group;
  }
}

/**
 * Implements template_preprocess_HOOK() for theme_acquia_lift_navbar_menu_tree().
 */
function template_preprocess_acquia_lift_navbar_menu_tree(&$variables) {
  $variables['tree'] = $variables['tree']['#children'];
}

/**
 * Implements template_preprocess_HOOK() for theme_acquia_lift_button_list().
 */
function template_preprocess_acquia_lift_radio_list(&$variables) {

  // Add a theme wrapper for each item in the list.
  foreach ($variables['element']['#options'] as $id => $title) {
    $variables['element'][$id]['#theme_wrappers'][] = 'acquia_lift_radio_list_item';
  }
}

/**
 * Returns HTML for a wrapper for the acquia lift navbar subtree.
 *
 * @param $variables
 *   An associative array containing:
 *   - tree: An HTML string containing the tree's items.
 *
 * @see template_preprocess_navbar_menu_tree()
 * @ingroup themeable
 */
function theme_acquia_lift_navbar_menu_tree(&$variables) {
  return '<ul class="' . acquia_lift_unibar_menu_class() . '">' . $variables['tree'] . '</ul>';
}

/**
 * Implements hook_help().
 */
function acquia_lift_help($path, $arg) {
  switch ($path) {
    case 'admin/help#acquia_lift':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Acquia Lift module provides machine-learning-based personalization for Drupal sites.') . '</p>';
      $output .= '<h3>' . t('Configuration') . '</h3>';
      $output .= '<p>' . t('Go to the !configlink to configure your Acquia Lift credentials.', array(
        '!configlink' => l(t('configuration settings page'), 'admin/config/content/personalize/acquia_lift'),
      )) . '</p>';
      $output .= '<h3>' . t('Manage your personalizations') . '</h3>';
      $output .= '<p>' . t('You can manage all of your Acquia Lift personalizations from the !campaignslink.', array(
        '!campaignslink' => l(t('personalization listing page'), 'admin/structure/personalize/manage'),
      )) . '</p>';
      return $output;
  }
}

/**
 * Implements hook_visitor_actions_ui_selector_ignore
 */
function acquia_lift_visitor_actions_ui_selector_ignore() {
  return array(
    'classes' => array(
      'acquia-lift-[a-zA-Z0-9\\_\\-]+',
    ),
    'ids' => array(
      'acquia-lift-[a-zA-Z0-9\\_\\-]+',
    ),
  );
}

/**
 * Returns updated campaign settings that can be used to update the client
 * interface in response to asynchronous events such as queue synchronization.
 *
 * In the future it could become necessary, useful, to pass parameters to
 * indicate the type of data to include.  The only current need is to return
 * the updated campaign basic information.
 */
function acquia_lift_settings_update() {
  $commands = array(
    ajax_command_settings(array(
      'acquia_lift' => array(
        'campaigns' => acquia_lift_get_campaign_details(),
      ),
    ), TRUE),
  );
  return personalize_ajax_commands_deliver($commands, TRUE);
}

/**
 * Returns an AJAX command to display a message box.
 *
 * @param $message
 *   The string message to display.  This message may include HTML text.
 * @param $seconds
 *   (optional) The number of seconds before the message box should be hidden.
 *   If 0 (default) then the box is shown until the user clicks to close it.
 * @return array
 *   A command array that can be returned via AJAX.
 */
function acquia_lift_command_messagebox($message, $seconds = 0) {
  if (is_array($message)) {
    $message = drupal_render($message);
  }

  // Add any messages set by drupal_set_message().
  $full_message = '';
  $queued_messages = drupal_get_messages();
  $delimiter = '<br />';
  foreach ($queued_messages as $messages) {
    $full_message .= implode($messages, $delimiter);
  }
  if (!empty($message)) {
    if (!empty($full_message)) {
      $full_message .= $delimiter;
    }
    $full_message .= $message;
  }
  return array(
    'command' => 'acquia_lift_message_box',
    'data' => array(
      'message' => $full_message,
      'seconds' => $seconds,
    ),
  );
}

/**
 * Returns an AJAX command to toggle the variation element selector.
 *
 * @param bool $start
 *   True to start the variation mode, FALSE to stop it.
 *
 * @return array
 *   The command to be included in AJAX response.
 */
function acquia_lift_command_variation_toggle($start = TRUE) {
  return array(
    'command' => 'acquia_lift_variation_toggle',
    'data' => array(
      'start' => $start,
    ),
  );
}

/**
 * Returns an AJAX command to preview a specific option set option (variation).
 *
 * @param string $agent_name
 *   The name of the campaign/agent to which the variation belongs.
 * @param int $osid
 *   The id of the option set to which the variation belongs.
 * @param string $option_id
 *   The id of the variation to display.
 *
 * @return array
 *   The command to be included in AJAX response.
 */
function acquia_lift_command_variation_preview($agent_name, $osid, $option_id) {
  return array(
    'command' => 'acquia_lift_variation_preview',
    'data' => array(
      'agentName' => $agent_name,
      'osid' => personalize_stringify_osid($osid),
      'optionId' => $option_id,
    ),
  );
}

/**
 * Returns an AJAX command to add or edit an element variation.
 *
 * @param stdClass $option_set
 *   The option set that owns the variation
 * @param $option_id
 *   The option id to edit or NULL if creating a new variation within the
 *   option set.
 * @return array
 *   The command to be included in AJAX response.
 */
function acquia_lift_command_variation_edit($option_set, $option_id = NULL) {
  return array(
    'command' => 'acquia_lift_variation_edit',
    'data' => array(
      'variationType' => $option_set->data['personalize_elements_type'],
      'selector' => $option_set->data['personalize_elements_selector'],
      'agentName' => $option_set->agent,
      'variationIndex' => empty($option_id) ? -1 : $option_id,
      'osid' => $option_set->osid,
    ),
  );
}

/**
 * Returns an AJAX command to force the update of option set data and then
 * reattach behaviors.
 *
 * @param array $option_sets
 *   An array of updated option set settings keyed by the osid.
 */
function acquia_lift_command_option_set_updates($option_sets) {
  return array(
    'command' => 'acquia_lift_option_set_updates',
    'data' => array(
      'option_sets' => $option_sets,
    ),
  );
}

/**
 * Returns an AJAX command to force the update of goals data within a campaign.
 *
 * @param string $agent_name
 *   The machine name for the campaign to update goal data.
 */
function acquia_lift_command_goal_updates($agent_name) {
  $settings = acquia_lift_get_campaign_details();
  return array(
    'command' => 'acquia_lift_goal_updates',
    'data' => array(
      'campaigns' => array(
        $agent_name => $settings[$agent_name],
      ),
    ),
  );
}

/**
 * Generate an array of campaign settings that are currently configured.
 * This administrative information is used for navigation and campaign
 * management.
 *
 * @return array
 *   An array of campaigns keyed by machine_name.
 */
function acquia_lift_get_campaign_details() {
  $campaigns =& drupal_static(__FUNCTION__, NULL);
  if (is_array($campaigns)) {
    return $campaigns;
  }
  module_load_include('inc', 'acquia_lift', 'acquia_lift.admin');
  $campaigns = array();

  // We need to provide information about all configured campaigns
  // and goals for the control UI.
  $actions = visitor_actions_get_actions();

  // List the campaigns in Drupal.settings, excluding completed campaigns.
  foreach (personalize_agent_load_multiple(array(), array(), FALSE, FALSE, 'label') as $agent) {
    if (acquia_lift_is_testing_agent($agent)) {
      continue;
    }
    $plugin = personalize_agent_load_agent($agent->machine_name);
    if (!$plugin) {
      continue;
    }
    $supports_goals = $plugin instanceof PersonalizeAgentGoalInterface;
    $supports_targeting = $plugin instanceof PersonalizeExplicitTargetingInterface;
    if ($supports_goals) {
      foreach (personalize_goal_load_by_conditions(array(
        'agent' => $agent->machine_name,
      )) as $goal) {
        if (isset($actions[$goal->action])) {
          $goals[$goal->action] = personalize_sanitize_string($actions[$goal->action]['label']);
        }
      }
    }
    $option_sets = personalize_option_set_load_by_agent($agent->machine_name);
    $option_set_types = array();
    foreach ($option_sets as $option_set) {
      $option_set_types[] = $option_set->plugin;
    }
    $targeting_option_set = acquia_lift_get_option_set_for_targeting($agent->machine_name);
    $disable_reports = empty($agent->started) || empty($targeting_option_set->targeting);
    $campaigns[$agent->machine_name] = array(
      'name' => $agent->machine_name,
      'label' => personalize_sanitize_string($agent->label),
      'type' => $agent->plugin,
      'links' => array(
        'view' => url('admin/structure/personalize/manage/' . $agent->machine_name),
        'edit' => url('admin/structure/personalize/manage/' . $agent->machine_name),
        'report' => $disable_reports ? '' : url('admin/structure/personalize/manage/' . $agent->machine_name . '/results'),
        'goals' => $supports_goals ? url('admin/structure/personalize/manage/' . $agent->machine_name . '/goals') : '',
        'targeting' => $supports_targeting ? url('admin/structure/personalize/manage/' . $agent->machine_name . '/targeting') : '',
        'scheduling' => url('admin/structure/personalize/manage/' . $agent->machine_name . '/scheduling'),
        'review' => url('admin/structure/personalize/manage/' . $agent->machine_name . '/review'),
      ),
      'optionSetTypes' => array_unique($option_set_types),
      'goals' => isset($goals) ? $goals : NULL,
      'editable' => acquia_lift_target_definition_changes_allowed($agent),
      'status' => personalize_agent_get_status($agent->machine_name),
    );

    // In the client, goals will render to an array when it is empty and an
    // object when it has data, so the no-data state must be NULL to keep
    // the type consistent as an object.
    unset($goals);
  }
  return $campaigns;
}

/**
 * Determines if the Lift debugger should be enabled on the page request.
 *
 * @return bool
 *   TRUE to include on the page, FALSE otherwise.
 */
function acquia_lift_debug_mode_enabled() {
  $enabled = personalize_debug_mode_enabled();
  if (!$enabled) {
    return FALSE;
  }
  if (!acquia_lift_is_configured()) {
    return FALSE;
  }

  // Check to see if there is a session variable for debug mode.
  $in_session = isset($_SESSION[ACQUIA_LIFT_INSPECTOR_URL]);

  // Check to see if it was requested
  if ($in_session) {

    //turns on the personalizeDebugMode cookie
    setcookie('personalizeDebugMode', 1, time() + 60, '/');

    // Check to see if it should be turned off from URL parameter.
    if (isset($_GET[ACQUIA_LIFT_INSPECTOR_URL]) && $_GET[ACQUIA_LIFT_INSPECTOR_URL] == FALSE) {
      unset($_SESSION[ACQUIA_LIFT_INSPECTOR_URL]);

      //deletes the personalizeDebugMode cookie
      setcookie('personalizeDebugMode', "", time() - 3600);
      return FALSE;
    }
    return TRUE;
  }
  else {

    // Check to see if it was requested via URL parameter.
    if (!empty($_GET[ACQUIA_LIFT_INSPECTOR_URL])) {
      $_SESSION[ACQUIA_LIFT_INSPECTOR_URL] = TRUE;
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Get the option set where targeting rules are defined.
 *
 * The assumption here is that if you have multiple option sets then targeting
 * is only defined on one of them as we currently do not support targeting for
 * MVTs.
 *
 * @param $agent
 *   A stdClass object representing the agent to get the option set for.
 * @return stdClass|NULL
 *   The option set to use for targeting rules or NULL if the agent has no option
 *   sets.
 */
function acquia_lift_get_option_set_for_targeting($agent_name, $reset = TRUE) {
  $option_sets = personalize_option_set_load_by_agent($agent_name, $reset);
  if (empty($option_sets)) {
    return NULL;
  }
  $targeting_osid = $targeting = NULL;
  foreach ($option_sets as $option_set) {
    if (empty($targeting) && !empty($option_set->targeting)) {
      $targeting_osid = $option_set->osid;
      $targeting = $option_set->targeting;
    }

    // If we have a personalize_elements option set then this one should be
    // used because the option ids matter and should not be clobbered by
    // other option ids.
    if ($option_set->plugin == 'elements') {
      $targeting_osid = $option_set->osid;
      break;
    }
  }
  if ($targeting_osid) {
    return $option_sets[$targeting_osid];
  }
  return reset($option_sets);
}

/**
 * Returns the list of test agents that are nested under the specified agent.
 *
 * @param $agent
 *   The agent to get nested test agents for.
 * @return array
 *   An array of agent names.
 */
function acquia_lift_get_nested_tests($agent) {
  if ($agent->plugin != 'acquia_lift_target') {
    return array();
  }
  $option_set = acquia_lift_get_option_set_for_targeting($agent->machine_name);
  if (empty($option_set) || empty($option_set->targeting)) {
    return array();
  }
  $agents = array();
  foreach ($option_set->targeting as $audience_name => $audience) {
    if (isset($audience['osid'])) {
      if ($option_set = personalize_option_set_load($audience['osid'])) {
        $agents[] = $option_set->agent;
      }
    }
  }
  return $agents;
}

/**
 * Identifies the fallback audience among the audiences provided.
 *
 * The machine name of the "everyone-else" audience may have changed, if tests
 * tests for the audience have changed, so it may have a suffix, like '-2'.
 *
 * @param $audiences
 *   An array of audience names.
 * @return string|bool
 */
function acquia_lift_get_fallback_audience_name($audiences) {
  foreach ($audiences as $audience) {
    if (strpos($audience, ACQUIA_LIFT_TARGETING_EVERYONE_ELSE) === 0) {
      return $audience;
    }
  }
  return FALSE;
}

/**
 * Checks whether or not Acquia Lift has been properly configured.
 * @return bool
 */
function acquia_lift_is_configured() {
  $account_info = acquia_lift_get_account_info();
  $api_configured = TRUE;
  $required_info = array(
    'public_key',
    'private_key',
    'api_url',
  );
  foreach ($required_info as $api_config) {
    if (empty($account_info[$api_config])) {
      $api_configured = FALSE;
    }
  }
  $js_path = variable_get('acquia_lift_profiles_js_path', '');
  return $api_configured && !empty($js_path);
}

/**
 * Helper function to cache the Acquia Lift version.
 */
function _acquia_lift_set_version() {

  // Cache the version in a variable so we can send it at not extra cost.
  $version = variable_get('acquia_lift_version', '7.x');
  $info = system_get_info('module', 'acquia_lift');

  // Send the version, or at least the core compatibility as a fallback.
  $new_version = isset($info['version']) ? (string) $info['version'] : (string) $info['core'];
  if ($version != $new_version) {
    variable_set('acquia_lift_version', $new_version);
  }
}

Functions

Namesort descending Description
acquia_lift_agent_delete_access Access callback for agent deletion.
acquia_lift_batch_sync_item Wrapper function around acquia_lift_sync_item that catches and logs exceptions thrown.
acquia_lift_campaign_edit_access Access callback for campaign edit page.
acquia_lift_command_goal_updates Returns an AJAX command to force the update of goals data within a campaign.
acquia_lift_command_messagebox Returns an AJAX command to display a message box.
acquia_lift_command_option_set_updates Returns an AJAX command to force the update of option set data and then reattach behaviors.
acquia_lift_command_variation_edit Returns an AJAX command to add or edit an element variation.
acquia_lift_command_variation_preview Returns an AJAX command to preview a specific option set option (variation).
acquia_lift_command_variation_toggle Returns an AJAX command to toggle the variation element selector.
acquia_lift_debug_mode_enabled Determines if the Lift debugger should be enabled on the page request.
acquia_lift_element_info Implements hook_element_info().
acquia_lift_flush_caches
acquia_lift_form_field_ui_field_edit_form_alter Implements hook_form_FORM_ID_alter().
acquia_lift_form_personalize_admin_form_alter Implements hook_form_FORM_ID_alter().
acquia_lift_form_personalize_agent_option_sets_form_alter Implements hook_form_FORM_ID_alter().
acquia_lift_form_personalize_blocks_form_alter Implements hook_form_FORM_ID_alter().
acquia_lift_form_personalize_campaign_wizard_alter Implements hook_form_FORM_ID_alter().
acquia_lift_form_personalize_elements_configuration_form_alter Implements hook_form_FORM_ID_alter().
acquia_lift_form_personalize_elements_form_alter Implements hook_form_FORM_ID_alter().
acquia_lift_form_personalize_status_change_form_alter Implements hook_form_FORM_ID_alter().
acquia_lift_get_account_info Retrieve the Acquia Lift Credentials.
acquia_lift_get_agent_types Returns the agent types this module provides.
acquia_lift_get_campaign_details Generate an array of campaign settings that are currently configured. This administrative information is used for navigation and campaign management.
acquia_lift_get_fallback_audience_name Identifies the fallback audience among the audiences provided.
acquia_lift_get_mvt_name_for_agent
acquia_lift_get_nested_tests Returns the list of test agents that are nested under the specified agent.
acquia_lift_get_option_set_editable_settings Helper function to generate editable JavaScript settings for an option set.
acquia_lift_get_option_set_for_targeting Get the option set where targeting rules are defined.
acquia_lift_help Implements hook_help().
acquia_lift_init Implements hook_init().
acquia_lift_is_configured Checks whether or not Acquia Lift has been properly configured.
acquia_lift_is_targeting_agent Returns whether or not the passed in agent is a targeting agent.
acquia_lift_is_testing_agent Returns whether or not the passed in agent is a testing agent.
acquia_lift_js_alter Implements hook_js_alter().
acquia_lift_legacy_access Access callback for the Lift legacy campaign list.
acquia_lift_libraries_info Implements hook_libraries_info().
acquia_lift_library Implements hook_library().
acquia_lift_library_alter Implements hook_library_alter().
acquia_lift_menu Implements hook_menu().
acquia_lift_menu_alter Implements hook_menu_alter().
acquia_lift_modernizr_info Implements hook_modernizr_info().
acquia_lift_module_implements_alter Implements hook_module_implements_alter().
acquia_lift_navbar Implements hook_navbar().
acquia_lift_navbar_pre_render Pre-render function for Acquia Lift unified navbar element.
acquia_lift_navbar_pre_render_item Pre-render function for an item within the unified navbar element.
acquia_lift_page_build Implements hook_page_build().
acquia_lift_percentage_validate Element validator for acquia_lift_percentage custom element.
acquia_lift_personalize_agent_delete Implements hook_personalize_agent_delete().
acquia_lift_personalize_agent_presave Implements hook_personalize_agent_presave().
acquia_lift_personalize_agent_type Implements hook_personalize_agent_type().
acquia_lift_personalize_agent_update_status Implements hook_personalize_agent_update_status().
acquia_lift_personalize_blocks_form_submit Submit handler for altered personalize_elements_form.
acquia_lift_personalize_campaign_action_links_alter Implements hook_personalize_campaign_action_links_alter().
acquia_lift_personalize_elements_confirmation_form_submit Submit handler for altered personalize_elements_configuration_form.
acquia_lift_personalize_elements_form_validate Validation handler for personalize_elements_form.
acquia_lift_personalize_fields_form_element_alter Implements hook_personalize_fields_form_element_alter().
acquia_lift_personalize_fields_form_element_validate Personalized fields element validation.
acquia_lift_personalize_goal_presave Implements hook_personalize_goal_presave().
acquia_lift_personalize_option_set_delete Implements hook_personalize_option_set_delete().
acquia_lift_personalize_option_set_insert Implements hook_personalize_option_set_insert().
acquia_lift_personalize_option_set_presave Implements hook_personalize_option_set_presave().
acquia_lift_personalize_option_set_render Implements hook_personalize_option_set_render().
acquia_lift_personalize_option_set_save Implements hook_personalize_option_set_save().
acquia_lift_personalize_wizard_steps_alter Implements hook_personalize_wizard_steps_alter().
acquia_lift_settings_update Returns updated campaign settings that can be used to update the client interface in response to asynchronous events such as queue synchronization.
acquia_lift_sync_item Queue callback function for making a request to Acquia Lift.
acquia_lift_system_info_alter Implements hook_system_info_alter().
acquia_lift_target_access Access callback for the Lift Target menu items.
acquia_lift_target_default_js_settings Returns the basic js settings needed by acquia_lift_target.
acquia_lift_target_validate_lock_step Validate a set of option sets to see if they are valid for lock-step handling.
acquia_lift_theme Implements hook_theme().
acquia_lift_unibar_menu_class Helper function to determine the menu class name to use for unibar structure.
acquia_lift_using_older_navbar Helper function to determine if an older version of navbar is in use.
acquia_lift_visitor_actions_ui_selector_ignore Implements hook_visitor_actions_ui_selector_ignore
template_preprocess_acquia_lift_navbar_menu_tree Implements template_preprocess_HOOK() for theme_acquia_lift_navbar_menu_tree().
template_preprocess_acquia_lift_radio_list Implements template_preprocess_HOOK() for theme_acquia_lift_button_list().
theme_acquia_lift_navbar_menu_tree Returns HTML for a wrapper for the acquia lift navbar subtree.
_acquia_lift_convert_libraries_to_library Converts a libraries module array to a hook_library array.
_acquia_lift_libraries_filter_null_values Determines if an item is empty or not.
_acquia_lift_libraries_get_preferred_variant_name Returns the variant that should be loaded based on order preference.
_acquia_lift_libraries_get_version Determines the version of a library.
_acquia_lift_libraries_variant_exists Libraries API variant callback.
_acquia_lift_missing_library_warning Helper function to display a message when a missing library is detected.
_acquia_lift_navigation_attach_assets Create and attach the assets for Acquia Lift navigation to an element on the page.
_acquia_lift_set_variable_if_not_set Helper function to set a non existing variable. This is needed during clean installs of acquia lift where the variables are not set yet and we want to set them with a default value. We can't do this during installation because the Acquia…
_acquia_lift_set_version Helper function to cache the Acquia Lift version.
_acquia_lift_using_unified_navbar Helper function to determine if the site is using the Acquia Lift unified navigation bar.

Constants