Skip to content
recommenderghost.module 22.3 KiB
Newer Older
 * @file
 * Drupal Module: RecommenderGhost
 * Adds the required Javascript to the bottom of all your Drupal pages
 * to allow tracking by the RecommenderGhost recommendation engine.
hhhc's avatar
hhhc committed
 * Adds 2 blocks to display recommendations on a node level
 *   - list view
 *   - picture view
hhhc's avatar
hhhc committed
 * JS Tracking part of this module is based on the Google Analytics module code.
hhhc's avatar
hhhc committed
 * @author: hhhc
// Remove tracking from all administrative pages,
// see http://drupal.org/node/34970.
define('RECOMMENDERGHOST_PAGES', "admin\nadmin/*\nbatch\nnode/add*\nnode/*/*\nuser/*/*");

// Define global API URL.
hhhc's avatar
hhhc committed
define('RECOMMENDERGHOST_API_URL', "api.recommenderghost.com");

/**
 * Implements hook_help().
 */
function recommenderghost_help($path, $arg) {
  switch ($path) {
    case 'admin/config/system/recommenderghost':
      return t('<a href="@rg_url">RecommenderGhost</a> is a free (registration required) service that allows you to add a recommender system to your website.',
         array('@rg_url' => 'http://www.recommenderghost.com'));
  }
}

/**
 * Implements hook_permission().
 */
function recommenderghost_permission() {
  return array(
    'administer recommenderghost' => array(
      'title' => t('Administer RecommenderGhost'),
      'description' => t('Perform maintenance tasks for RecommenderGhost.'),
    ),
    'use PHP for recommenderghost' => array(
      'title' => t('Use PHP for tracking visibility'),
      'description' => t('Enter PHP code in the field for tracking visibility settings.'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Implements hook_menu().
 */
function recommenderghost_menu() {
  $items['admin/config/system/recommenderghost'] = array(
    'title' => 'RecommenderGhost',
    'description' => 'Configure tracking and recommendation engine for RecommenderGhost.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('recommenderghost_admin_settings_form'),
    'access arguments' => array('administer recommenderghost'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'recommenderghost.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_field_extra_fields().
 */
function recommenderghost_field_extra_fields() {
  $extra['user']['user']['form']['recommenderghost'] = array(
    'label' => t('RecommenderGhost configuration'),
    'description' => t('RecommenderGhost module form element.'),
    'weight' => 3,
  );
  return $extra;
}

/**
 * Implements hook_page_alter().
 *
 * Inserts JavaScript to the appropriate scope/region of the page.
 */
function recommenderghost_page_alter(&$page) {
  global $user;
  // Following privacy policy of RecommenderGhost we need to
  // encode user ids into non-revertable formatOutput.
  $uid = sha1($user->uid);

  $apikey = variable_get('recommenderghost_apikey', '');
  $websiteid = variable_get('recommenderghost_websiteid', '');

  $is_node = FALSE;
  if (arg(0) == 'node' && is_numeric(arg(1))) {
  }

  // 1. Check that api key and website id have a value.
  // 2. Track page views based on visibility value.
  // 3. Check if we should track the currently active user's role.
  if ($is_node && !empty($apikey) && !empty($websiteid) && _recommenderghost_visibility_pages() && _recommenderghost_visibility_user($user) && _recommenderghost_visibility_content_types($node)) {
    // If node view then define the node variables we want to track.
    if ($is_node) {
      // Get node id and site URL.
      $item_id = $node->nid;
      $item_url = $_SERVER["REQUEST_URI"];
      $title = $node->title;
      if (!$title) {
        $title = "";

      $img_field = variable_get('recommenderghost_image_token_field', '');
      $path = token_replace($img_field, array('node' => $node));
      // Filter unchanged tokens.
      $clean = preg_replace('/\[(.*)\]/is', "", $path);
      $clean_array = explode(",", $clean);

      // Only use relative paths as recommended.
      $url = parse_url(trim($clean_array[0]));
      $item_image_url = $url['path'];

    // We allow different methods. Default to 'JS' but
    // allow users to override if they really need to.
    $method = variable_get('recommenderghost_tracking_method', 'JS');
    $mapping = variable_get('recommenderghost_mapping_' . $node->type, 'ITEM');
    // Check which method (REST or JS) to use for tracking.
    // REST will only work if no caching (eg Varnish, Boost) will be used.
    if ($method == "REST" && $is_node) {
hhhc's avatar
hhhc committed
      $recommendations = recommenderghost_call_api("view", array(
        "itemid" => $item_id,
        "itemdescription" => $title,
        "itemurl" => $item_url,
        "itemimageurl" => $item_image_url,
        "sessionid" => session_id(),
hhhc's avatar
hhhc committed
        ));
    }
    else {
      // We allow different scopes for JS. Default to 'header' but allow user
      // to override if they really need to.
      $scope = variable_get('recommenderghost_js_scope', 'header');

      // Add any custom code snippets if specified.
      $codesnippet_before = variable_get('recommenderghost_codesnippet_before', '');
hhhc's avatar
hhhc committed
      $custom_callback = variable_get('recommenderghost_drawingcallback', '');

      // Build tracker code.
      $script_tracker = 'var apiKey = ' . drupal_json_encode($apikey) . ';';
      $script_tracker .= 'var tenantId = ' . drupal_json_encode($websiteid) . ';';
hhhc's avatar
hhhc committed
      if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
        $script_tracker .= 'var easyrecApiUrl="https://' . RECOMMENDERGHOST_API_URL . '/api/1.0/json/";';
      }
hhhc's avatar
hhhc committed
      if (!empty($custom_callback)) {
        $script .= $custom_callback;
      }

      if ($is_node) {
        $script .= "
        easyrec_sendAction('view',{
            itemId:'" . $item_id . "',
            itemUrl:'" . check_url($item_url) . "',
            itemDescription: '" . $title . "',";
        if ($item_image_url) {
          $script .= "
            itemImageUrl:'" . check_url($item_image_url) . "',";
            itemType:'" . $mapping . "',
          });
          ";
      }

      if (!empty($codesnippet_after)) {
        $script .= $codesnippet_after;
      }
hhhc's avatar
hhhc committed

      // Add SSL protocol.
      $protocol = "http://";
      if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
        // When using ISAPI with IIS, the value will be off if the
        // request was not made through the HTTPS protocol.
        $protocol = "https://";
      }
      // JS file and tracker vars need to be in header so that blocks work.
      // Code for actual tracking can be put to footer.
hhhc's avatar
hhhc committed
      drupal_add_js($protocol . RECOMMENDERGHOST_API_URL . "/api-js/ghost.js",
                    array('scope' => 'header'));
      drupal_add_js($script_tracker,
                    array('scope' => 'header', 'type' => 'inline'));
      if ($script && $is_node) {
        drupal_add_js($script, array('scope' => $scope, 'type' => 'inline'));
      }
    }
 * Return visibility setting based on current page.
 */
function _recommenderghost_visibility_pages() {
  static $page_match;

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

    $visibility = variable_get('recommenderghost_visibility_pages', 0);
    $setting_pages = variable_get('recommenderghost_pages', RECOMMENDERGHOST_PAGES);

    // Match path if necessary.
    if (!empty($setting_pages)) {
      // Convert path to lowercase. This allows comparison of the same path
      // with different case. Ex: /Page, /page, /PAGE.
      $pages = drupal_strtolower($setting_pages);
      if ($visibility < 2) {
        // Convert the Drupal path to lowercase.
        $path = drupal_strtolower(drupal_get_path_alias($_GET['q']));
        // Compare the lowercase internal and lowercase path alias (if any).
        $page_match = drupal_match_path($path, $pages);
        if ($path != $_GET['q']) {
          $page_match = $page_match || drupal_match_path($_GET['q'], $pages);
        }
        // When $visibility has a value of 0, the tracking code is displayed on
        // all pages except those listed in $pages. When set to 1, it
        // is displayed only on those pages listed in $pages.
        $page_match = !($visibility xor $page_match);
      }
      elseif (module_exists('php')) {
        $page_match = php_eval($setting_pages);
      }
      else {
        $page_match = FALSE;
      }
    }
    else {
      $page_match = TRUE;
    }

  }
  return $page_match;
}

/**
 * Return visibility settings for current role.
 */
function _recommenderghost_visibility_roles($account) {

  $visibility = variable_get('recommenderghost_visibility_roles', 0);
  $enabled = $visibility;
  $roles = variable_get('recommenderghost_roles', array());

  if (array_sum($roles) > 0) {
    // One or more roles are selected.
    foreach (array_keys($account->roles) as $rid) {
      // Is the current user a member of one of these roles?
      if (isset($roles[$rid]) && $rid == $roles[$rid]) {
        // Current user is a member of a role that should be tracked/
        // excluded from tracking.
        $enabled = !$visibility;
        break;
      }
    }
  }
  else {
    // No role is selected for tracking, therefore all roles should be tracked.
    $enabled = TRUE;
  }

  return $enabled;
}

/**
 * Return visibility settings for current content type.
 */
function _recommenderghost_visibility_content_types($node) {

  $visibility = variable_get('recommenderghost_visibility_content_types', 0);
  $enabled = $visibility;
  $content_types = variable_get('recommenderghost_content_types', array());

  if (count($content_types) > 0) {
    // One or more content_types are selected.
    $type = $node->type;
    if (isset($content_types[$type]) && $type === $content_types[$type]) {
      // Current node is a member of content type that should be tracked/
      // excluded from tracking.
      $enabled = !$visibility;
    }
    else {
      $enabled = $visibility;
    }
  }
  else {
    // No content_type is selected for tracking,
    // therefore all content_types should be tracked.
    $enabled = TRUE;
  }
  return $enabled;
}

/**
 * Tracking visibility check for an user object.
 *
 * @param object $account
 *   A user object containing an array of roles to check.
 *   A decision on if the current user is being tracked by RecommenderGhost.
 */
function _recommenderghost_visibility_user($account) {

  $enabled = FALSE;

  // Is current user a member of a role that should be tracked?
  if (_recommenderghost_visibility_roles($account)) {

    // Use the user's block visibility setting, if necessary.
    if (($custom = variable_get('recommenderghost_custom', 0)) != 0) {
      if ($account->uid && isset($account->data['recommenderghost']['custom'])) {
        $enabled = $account->data['recommenderghost']['custom'];
      }
      else {
        $enabled = ($custom == 1);
      }
    }
    else {
      $enabled = TRUE;
    }

  }

  return $enabled;
}

/**
 * Implements a central REST query. Returns json of the result.
hhhc's avatar
hhhc committed
function recommenderghost_call_api($function, $parameters) {
  $apikey = variable_get('recommenderghost_apikey', '');
  $websiteid = variable_get('recommenderghost_websiteid', '');
hhhc's avatar
hhhc committed
  $url = RECOMMENDERGHOST_API_URL . "/api/1.0/json/" . $function;
  $url .= "?apikey=$apikey&tenantid=$websiteid&";
  $url .= http_build_query($parameters);
  $response = drupal_http_request(($url), array(
  return json_decode($response->data);
function recommenderghost_block_info() {
  $blocks['rg_also_viewed_list'] = array(
    'info' => t('RecommenderGhost: Recommendations List View'),
    'cache' => DRUPAL_CACHE_PER_PAGE,
  );
  $blocks['rg_also_viewed_pictures'] = array(
    'info' => t('RecommenderGhost: Recommendations Picture View'),
    'cache' => DRUPAL_CACHE_PER_PAGE,
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 *
 * The block defines various blocks for showing recommendations.
function recommenderghost_block_view($delta = '') {

  // Define available recommendations.
  $recommendation_types = array(
    "otherusersalsoviewed" => t("Other users also viewed"),
    "otherusersalsobought" => t("Other users also bought"),
    "recommendationsforuser" => t("Recommendations for user"),
    "related items" => t("Related items"),
  );
  // Following privacy policy of RecommenderGhost we need to
  // encode user ids into non-revertable formatOutput.
  $uid = sha1($user->uid);
hhhc's avatar
hhhc committed
  $tracking_method = variable_get('recommenderghost_tracking_method', 'JS');

  // Only show the recommendations for node views.
  if (arg(0) == 'node' && is_numeric(arg(1)) && !arg(2)) {
    $node = node_load(arg(1));
    $nid = arg(1);
  $item_type = variable_get('recommenderghost_mapping_' . $node->type, 'ITEM');
hhhc's avatar
hhhc committed
  $use_custom_callback = variable_get('recommenderghost_use_custom_drawingcallback_' . $delta, FALSE);

      if (user_access('access content')) {
        $div = "recommenderDivAlsoViewedList";
hhhc's avatar
hhhc committed
        ($tracking_method == "JS" && $use_custom_callback) ? $drawing_callback = "drawCustomCallbackToDiv" : $drawing_callback = "drawRecommendationList";
        $recommendationtype = variable_get('recommenderghost_block_recommendationtype_rg_also_viewed_list', 'otherusersalsoviewed');
        $nb_of_recommendations = variable_get('recommenderghost_block_number_of_recommendations_rg_also_viewed_list', '5');
        $limit_same_type = variable_get('recommenderghost_block_same_itemtype_rg_also_viewed_list', FALSE);
        $with_uid = variable_get('recommenderghost_block_with_uid_rg_also_viewed_list', FALSE);
        $block['subject'] = $recommendation_types[$recommendationtype];
    case 'rg_also_viewed_pictures':
      if (user_access('access content')) {
        $div = "recommenderDivAlsoViewedPictures";
hhhc's avatar
hhhc committed
        ($tracking_method == "JS" && $use_custom_callback) ? $drawing_callback = "drawCustomCallbackToDiv" : $drawing_callback = "drawRecommendationListWithPictures";
        $recommendationtype = variable_get('recommenderghost_block_recommendationtype_rg_also_viewed_pictures', 'otherusersalsoviewed');
        $nb_of_recommendations = variable_get('recommenderghost_block_number_of_recommendations_rg_also_viewed_pictures', '5');
        $limit_same_type = variable_get('recommenderghost_block_same_itemtype_rg_also_viewed_pictures', FALSE);
        $with_uid = variable_get('recommenderghost_block_with_uid_rg_also_viewed_pictures', FALSE);
        $block['subject'] = $recommendation_types[$recommendationtype];
hhhc's avatar
hhhc committed
  if ($tracking_method == "REST") {
hhhc's avatar
hhhc committed
    // Grab recommendations from website using REST.
hhhc's avatar
hhhc committed
      "numberOfResults" => $nb_of_recommendations,
      "itemtype" => $item_type,
      "itemid" => $nid,
hhhc's avatar
hhhc committed
    );
    ($limit_same_type) ? $options["requesteditemtype"] = $item_type : "";
    ($recommendationtype == "recommendationsforuser" || $with_uid) ? $options["userid"] = $uid : "";
    $recommendations = recommenderghost_call_api($recommendationtype, $options);
    // No recommendations for this item.
hhhc's avatar
hhhc committed
    if (!isset($recommendations->recommendeditems->item)) {
      return;
    }
    // If only one result then change array structure.
    if (isset($recommendations->recommendeditems->item->creationDate)) {
      $items[0] = $recommendations->recommendeditems->item;
    }
    else {
      $items = $recommendations->recommendeditems->item;
    }

    // Correct some data.
    $new_items = array();
hhhc's avatar
hhhc committed
    foreach ($items as $item) {
      $item->imageUrl = isset($item->imageUrl) ? $item->imageUrl : "http://www.recommenderghost.com/sites/all/themes/bootstrap/img/no_image.gif";
      $new_items[] = $item;
hhhc's avatar
hhhc committed
    if ($drawing_callback == "drawRecommendationList") {
      $block['content'] = theme('recommenderghost_list_view', array('items' => $new_items));
hhhc's avatar
hhhc committed
    }
      $block['content'] = theme('recommenderghost_picture_view', array('items' => $new_items));
hhhc's avatar
hhhc committed
  else {
hhhc's avatar
hhhc committed
    // JAVASCRIPT implementation.
hhhc's avatar
hhhc committed
    $delta_block = str_replace("_", "-", $delta);
    $js = "<script>";
    ($drawing_callback == "drawRecommendationListWithPictures" || $drawing_callback == "drawRecommendationList") ? $drawing_callback .= "ToDiv" : NULL;
    $js .= "
      function recommenderghost_drawingCallback_forwarding_$delta(json) {
hhhc's avatar
hhhc committed
        if (json.recommendeditems!=null) {
hhhc's avatar
hhhc committed
          $drawing_callback(json,'recommenderDiv$delta');
        } else {
         document.getElementById('block-recommenderghost-$delta_block').style.display='none';
        }
hhhc's avatar
hhhc committed
      }  
hhhc's avatar
hhhc committed
    ";
    $js .= "
    easyrec_getRecommendations('$recommendationtype', {
hhhc's avatar
hhhc committed
         itemId:'$nid',
         numberOfResults: $nb_of_recommendations,
hhhc's avatar
hhhc committed
         drawingCallback:'recommenderghost_drawingCallback_forwarding_$delta',
         itemType:'$item_type',";
    ($limit_same_type) ? $js .= "
         requestedItemType:'$item_type'" : "";
    ($recommendationtype == "recommendationsforuser" || $with_uid) ? $js .= "
         userId:'$uid'" : "";
    $js .= "
hhhc's avatar
hhhc committed
      }
    );
    </script>
hhhc's avatar
hhhc committed
    <div id='recommenderDiv$delta'></div>";
hhhc's avatar
hhhc committed
    $block['content'] = $js;

/**
 * Implements hook_theme().
 */
function recommenderghost_theme() {
  return array(
    'recommenderghost_list_view' => array(
      'template' => 'recommenderghost_list_view',
      'variables' => array('items' => NULL),
    ),
    'recommenderghost_picture_view' => array(
      'template' => 'recommenderghost_picture_view',
 * Implements template_preprocess_HOOK.
function template_preprocess_recommenderghost_list_view(&$variables) {
  $variables['theme_hook_suggestions'][] = 'recommenderghost_list_view__block';
  foreach ($variables['items'] as $key => $one) {
    $new = array();
    $new["creationDate"] = $one->creationDate;
    $new["description"] = check_plain($one->description);
    $new["imageUrl"] = check_url($one->imageUrl);
    $new["id"] = $one->id;
    $new["itemType"] = $one->itemType;
    $new["url"] = check_url($one->url);
    $new["value"] = $one->value;
    $variables["items"][$key] = $new;
 * Implements template_preprocess_HOOK.
function template_preprocess_recommenderghost_picture_view(&$variables) {
  template_preprocess_recommenderghost_list_view($variables);
  $variables['theme_hook_suggestions'][] = 'recommenderghost_list_pictures__block';

/**
 * Implements hook_block_configure().
 */
function recommenderghost_block_configure($delta="") {
  $form['blockconfig'] = array(
    '#type' => 'fieldset',
    '#title' => t('RecommenderGhost block settings'),
  );

  // Define available recommendations.
  $recommendation_types = array(
    "otherusersalsoviewed" => t("Other users also viewed"),
    "otherusersalsobought" => t("Other users also bought"),
    "recommendationsforuser" => t("Recommendations for user"),
    "related items" => t("Related items"),
  );
  $form['blockconfig']['recommenderghost_block_recommendationtype_' . $delta] = array(
    '#type' => 'select',
    '#title' => t('Which recommendation type would you like to show?'),
    '#description' => t('This parameter determines which analysis method is used for retrieving recommendations from RecommenderGhost.'),
    '#default_value' => variable_get('recommenderghost_block_recommendationtype_' . $delta, 'otherusersalsoviewed'),
    '#options' => $recommendation_types,
  );

  // Define nb of recommendations.
  $nb = array(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
  $form['blockconfig']['recommenderghost_block_number_of_recommendations_' . $delta] = array(
    '#title' => t('How many recommendations would you like to show?'),
    '#description' => t('This parameter determines the number of recommendations retrieved from RecommenderGhost.'),
    '#default_value' => variable_get('recommenderghost_block_number_of_recommendations_' . $delta, '5'),
hhhc's avatar
hhhc committed
  // Show drawing callback option for JS method only.
  if (variable_get('recommenderghost_tracking_method', 'JS') == "JS") {
    $form['blockconfig']['recommenderghost_use_custom_drawingcallback_' . $delta] = array(
      '#type' => 'checkbox',
      '#title' => t('Use custom drawing callback function?'),
      '#description' => t('You can define your own Javascript drawing callback function in the advanced configuration section of RecommenderGhost. Tick this box to use the custom one.'),
      '#default_value' => variable_get('recommenderghost_use_custom_drawingcallback_' . $delta, FALSE),
    );
  }

  // Define to limit item type.
  $form['blockconfig']['recommenderghost_block_same_itemtype_' . $delta] = array(
    '#type' => 'checkbox',
    '#title' => t('Limit recommendations to same item type?'),
    '#description' => t('If you have several item types (eg CD, DVD) configured in RecommenderGhost you can filter recommendations to only show the ones from the same item type as current one. This applies only if you have configured more than the standard item type ("ITEM").'),
    '#default_value' => variable_get('recommenderghost_block_same_itemtype_' . $delta, FALSE),
  );

  // Define to personlize to current user (if logged in).
  $form['blockconfig']['recommenderghost_block_with_uid_' . $delta] = array(
    '#type' => 'checkbox',
    '#title' => t('Filter items user already viewed?'),
    '#description' => t('If set any provided items viewed by this user are suppressed.'),
    '#default_value' => variable_get('recommenderghost_block_with_uid_' . $delta, FALSE),
  return $form;
}

/**
 * Implements hook_block_save().
 */
function recommenderghost_block_save($delta='', $edit=array()) {
  variable_set('recommenderghost_block_recommendationtype_' . $delta, $edit['recommenderghost_block_recommendationtype_' . $delta]);
  variable_set('recommenderghost_block_number_of_recommendations_' . $delta, $edit['recommenderghost_block_number_of_recommendations_' . $delta]);
hhhc's avatar
hhhc committed
  variable_set('recommenderghost_use_custom_drawingcallback_' . $delta, $edit['recommenderghost_use_custom_drawingcallback_' . $delta]);
  variable_set('recommenderghost_block_same_itemtype_' . $delta, $edit['recommenderghost_block_same_itemtype_' . $delta]);
  variable_set('recommenderghost_block_with_uid_' . $delta, $edit['recommenderghost_block_with_uid_' . $delta]);