You are here

simplenews_statistics.module in Simplenews Statistics 6.3

Main simplenews statistics file.

File

simplenews_statistics.module
View source
<?php

/**
 * @file
 * Main simplenews statistics file.
 */

/**
 * Implements hook_menu().
 */
function simplenews_statistics_menu() {
  $items['admin/settings/simplenews/statistics'] = array(
    'title' => 'Statistics',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_statistics_admin_settings_form',
    ),
    'access arguments' => array(
      'administer newsletter statistics',
    ),
    'file' => 'simplenews_statistics.admin.inc',
    'weight' => -7.9,
  );

  // Menu callbacks for processing clicks and opens
  $items['simplenews/statistics/view'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'simplenews_statistics_view',
    'access arguments' => array(
      'access content',
    ),
  );
  $items['simplenews/statistics/click'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'simplenews_statistics_click',
    'access arguments' => array(
      'access content',
    ),
  );
  return $items;
}

/**
 * Implements hook_perm().
 */
function simplenews_statistics_perm() {
  $perms = array(
    'administer newsletter statistics',
    'view newsletters statistics',
  );
  return $perms;
}

/**
 * Access for newsletter statistics.
 */
function simplenews_statistics_access($node = NULL) {

  // If the user has the global permission, allow access.
  if (user_access('view newsletters statistics')) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_help().
 */
function simplenews_statistics_help($path, $arg) {
  switch ($path) {
    case 'admin/help#simplenews_statistics':
      $help = '<p>' . t('Simplenews Statistics gathers the open rate and CTR of a send newsletter.') . "<br />\n";
      $help .= t("Open Rate is the number of people who open a newsletter divided by the amount of subscriptions.") . "<br />\n";
      $help .= t("CTR (click-through rate) is the number of people who clicked a link in a newsletter divided by the amount of subscriptions") . "</p>\n";
      return $help;
    case 'admin/content/simplenews/statistics':
      $help = '<p>' . t('Shows the open rate and CTR for your newsletters.') . "<p>\n";
      return $help;
    case 'admin/content/simplenews/statistics/%':
      $help = '<p>' . t('Shows who and how many times the newsletter has been openend.') . "<p>\n";
      return $help;
    case 'admin/content/simplenews/statistics/%/clicks':
      $help = '<p>' . t('Listing of all clicks for the newsletter.') . "<p>\n";
      return $help;
  }
}

/**
 * Gathers the opens.
 */
function simplenews_statistics_view() {
  $stat = _simplenews_statistics_decode($_GET);
  if ($stat['mail'] && $stat['nid'] && variable_get('simplenews_statistics', 1)) {

    // Process the open for statistics.
    _simplenews_statistics_open_add($stat);
  }
}

/**
 * Gathers the clicks.
 */
function simplenews_statistics_click() {
  $stat = _simplenews_statistics_decode($_GET);
  if ($stat['mail'] && $stat['nid'] && $stat['url']) {

    // Process the click for statistics.
    if (variable_get('simplenews_statistics', 1)) {
      _simplenews_statistics_click_add($stat);
    }

    // Add Google Aanalytics tracking.
    _simplenews_statistics_add_ga($stat['url'], $stat['nid']);

    // Proceed to the actual link.
    drupal_goto(urldecode($stat['url']));
  }
  drupal_goto();
}

/**
 * Implements hook_mail_alter().
 *
 * Adds a hidden image to the body and counts the amount of emails send.
 */
function simplenews_statistics_mail_alter(&$message) {
  if (($message['id'] == 'simplenews_node' || $message['id'] == 'simplenews_test') && $message['params']['context']['node']->simplenews['s_format'] == 'html') {
    $nid = $message['params']['context']['node']->nid;
    $mail = $message['params']['context']['account']->mail;

    // Parse body.
    _simplenews_statistics_parse_links($message['body'], $nid, $mail);

    // Add view image.
    _simplenews_statistics_image_tag($message['body'], $nid, $mail);

    // Count the number of sent mails for this newsletter
    if (variable_get('simplenews_statistics', 1)) {
      if (!db_fetch_array(db_query('SELECT * FROM {simplenews_statistics} WHERE nid = %d', $nid))) {
        db_query('INSERT INTO {simplenews_statistics} (nid, send) VALUES (%d, %d)', $nid, 1);
      }
      else {
        db_query('UPDATE {simplenews_statistics} SET send = send+1 WHERE nid = %d', $nid);
      }
    }
  }
}

/**
 * Parse links in the body.
 */
function _simplenews_statistics_parse_links(&$body, $nid, $mail) {
  if (is_array($body)) {
    foreach ($body as $key => $element) {
      _simplenews_statistics_parse_links($body[$key], $nid, $mail);
    }
  }
  else {

    // Replace links.
    $pattern = '/(<a[^>]+href=")([^"]*)/emi';
    $body = preg_replace($pattern, '"\\1"._simplenews_statistics_replace_url("\\2", $nid, $mail)', $body);
  }
}

/**
 * Add hidden image for view statistics.
 */
function _simplenews_statistics_image_tag(&$body, $nid, $mail) {
  if (is_array($body)) {
    foreach ($body as $key => $element) {
      _simplenews_statistics_image_tag($body[$key], $nid, $mail);

      // Only add 1 image tag.
      return;
    }
  }
  else {
    require_once drupal_get_path('module', 'simplenews_statistics') . '/includes/rc4.inc';

    // Add hidden image.
    $pars = 'nid=' . check_plain($nid) . '&mail=' . check_plain($mail);
    $pars = _simplenews_statistics_rc4Encrypt(simplenews_private_key(), $pars);
    $pars_hash = md5($pars);
    $simplenews_statistics_internal_url = url('simplenews/statistics/view', array(
      'absolute' => TRUE,
      'query' => array(
        'p' => _simplenews_statistics_encode_parameter($pars),
        'h' => _simplenews_statistics_encode_parameter($pars_hash),
      ),
    ));

    // Use the "full-url"-option to get an absolute URL.
    $shortened_url = shorturl_shorten($simplenews_statistics_internal_url, TRUE);
    $body .= '<img src="' . $shortened_url . '" width="1" height="1">';
  }
}

/**
 * Alter link to go through statistics.
 */
function _simplenews_statistics_replace_url($match, $nid, $mail) {

  // Do not replace anchor-links
  if (substr($match, 0, 1) == '#') {
    return $match;
  }
  $track_emailaddress = variable_get('simplenews_statistics_track_emailaddress', 1);
  if ($track_emailaddress == 0) {
    if (substr($match, 0, 7) == 'mailto:') {
      return $match;
    }
  }
  require_once drupal_get_path('module', 'simplenews_statistics') . '/includes/rc4.inc';
  $pars = 'nid=' . check_plain($nid) . '&mail=' . check_plain($mail) . '&url=' . check_url($match);
  $pars = _simplenews_statistics_rc4Encrypt(simplenews_private_key(), $pars);
  $pars_hash = md5($pars);
  $simplenews_statistics_internal_url = url('simplenews/statistics/click', array(
    'absolute' => TRUE,
    'query' => array(
      'p' => _simplenews_statistics_encode_parameter($pars),
      'h' => _simplenews_statistics_encode_parameter($pars_hash),
    ),
  ));

  // Use the "full-url"-option to get an absolute URL.
  $shortened_url = shorturl_shorten($simplenews_statistics_internal_url, TRUE);
  return $shortened_url;
}

/**
 * Add Google Analytics codes to a raw encoded/encrypted URL.
 */
function _simplenews_statistics_add_ga(&$url, $nid) {
  $ga_tracking = variable_get('simplenews_statistics_ga', 0);
  if ($ga_tracking == 1 && module_exists('googleanalytics')) {
    $campaign = variable_get('simplenews_statistics_ga_utm_campaign', '!newsletter_title');
    if ($campaign == '!newsletter_title') {
      $node = node_load($nid);
      $campaign = $node->title;
    }
    if (variable_get('simplenews_statistics_ga_use_hash', 0)) {
      $url .= '#utm_source=' . drupal_urlencode(variable_get('simplenews_statistics_ga_utm_source', 'newsletter'));
    }
    else {
      if (stristr($url, '?')) {
        $url .= '&utm_source=' . drupal_urlencode(variable_get('simplenews_statistics_ga_utm_source', 'newsletter'));
      }
      else {
        $url .= '?utm_source=' . drupal_urlencode(variable_get('simplenews_statistics_ga_utm_source', 'newsletter'));
      }
    }
    $url .= '&utm_medium=' . drupal_urlencode(variable_get('simplenews_statistics_ga_utm_medium', 'email'));
    $url .= '&utm_campaign=' . drupal_urlencode($campaign);
  }
}

/**
 * Decode a request.
 */
function _simplenews_statistics_decode($values) {
  $pars = $values['p'];
  $pars_hash = $values['h'];
  if (isset($pars) && isset($pars_hash)) {
    $pars = _simplenews_statistics_decode_parameter($pars);
    $pars_hash = _simplenews_statistics_decode_parameter($pars_hash);
    if (md5($pars) == $pars_hash) {
      require_once drupal_get_path('module', 'simplenews_statistics') . '/includes/rc4.inc';
      $pars = _simplenews_statistics_rc4Decrypt(simplenews_private_key(), $pars);
      parse_str($pars, $stat);
    }
  }
  foreach ($stat as $idx => $stat_param) {
    if ($idx != 'nid' && $idx != 'mail' && $idx != 'url') {
      $idx = str_replace('amp;', '', $idx);
      if (stristr($stat['url'], '?')) {
        $stat['url'] = $stat['url'] . '&' . $idx . '=' . $stat_param;
      }
      else {
        $stat['url'] = $stat['url'] . '?' . $idx . '=' . $stat_param;
      }
    }
  }
  return $stat;
}

/**
 * Encode parameter so clicks and opens can not be faked
 */
function _simplenews_statistics_encode_parameter($par) {
  return strtr(base64_encode(addslashes(gzcompress(serialize($par), 9))), '+/=', '-_,');
}

/**
 * Decode encode string so we can work with the data
 */
function _simplenews_statistics_decode_parameter($par) {
  return unserialize(gzuncompress(stripslashes(base64_decode(strtr($par, '-_,', '+/=')))));
}

/**
 * Register click.
 */
function _simplenews_statistics_click_add($stat) {

  // Check if this is a unique click for the newsletter-mail combination.
  $query = 'SELECT nid FROM {simplenews_statistics_clicks} WHERE email=\'%s\' AND nid=%d LIMIT 1';
  $result = db_query($query, $stat['mail'], $stat['nid']);
  $row = db_fetch_array($result);
  if (empty($row)) {
    db_query('UPDATE {simplenews_statistics} SET user_unique_click_through=user_unique_click_through+1 WHERE nid=%d', $stat['nid']);
  }

  // Update the total click amount.
  db_query('UPDATE {simplenews_statistics} SET total_clicks=total_clicks+1 WHERE nid=%d', $stat['nid']);

  // Register the indivudual click.
  db_query('INSERT INTO {simplenews_statistics_clicks} (email, nid, url, timestamp) VALUES (\'%s\', %d, \'%s\', %d)', $stat['mail'], $stat['nid'], urldecode($stat['url']), time());
}

/**
 * Add open to total.
 */
function _simplenews_statistics_open_add($stat) {

  // Check if this is a unique open for the newsletter-mail combination.
  $query = 'SELECT nid FROM {simplenews_statistics_opens} WHERE email=\'%s\' AND nid=%d LIMIT 1';
  $result = db_query($query, $stat['mail'], $stat['nid']);
  $row = db_fetch_array($result);
  if (empty($row)) {
    db_query('UPDATE {simplenews_statistics} SET unique_opens=unique_opens+1 WHERE nid=%d', $stat['nid']);
  }

  // Update the total opens amount.
  db_query('UPDATE {simplenews_statistics} SET total_opens=total_opens+1 WHERE nid=%d', $stat['nid']);

  // Register the individual open.
  db_query('INSERT INTO {simplenews_statistics_opens} (email, nid, timestamp) VALUES (\'%s\', %d, %d)', $stat['mail'], $stat['nid'], time());
}

/**
 * Implements hook_views_api().
 */
function simplenews_statistics_views_api() {
  return array(
    'api' => '2.0',
    'path' => drupal_get_path('module', 'simplenews_statistics') . '/includes/views',
  );
}

/**
 * Implements hook_nodeapi(). 
 */
function simplenews_statistics_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if ($op == 'delete') {
    $query = 'DELETE FROM {simplenews_statistics} WHERE nid=%d';
    db_query($query, $node->nid);
    $query = 'DELETE FROM {simplenews_statistics_clicks} WHERE nid=%d';
    db_query($query, $node->nid);
    $query = 'DELETE FROM {simplenews_statistics_opens} WHERE nid=%d';
    db_query($query, $node->nid);
  }
}

/**
 * Implements hook_cron().
 */
function simplenews_statistics_cron() {
  $days = variable_get('simplenews_statistics_decrypt_shorturl_days', 0);
  if (is_numeric($days) && $days > 0) {
    $query = 'SELECT lid, orig_url FROM {shorturl_link} WHERE created>%d AND orig_url LIKE "%/simplenews/statistics/click?p=%" LIMIT 25000';
    $result = db_query($query, $days * 86400);
    $n = 0;
    while ($row = db_fetch_array($result)) {
      $values = array();
      $q = parse_url($row['orig_url'], PHP_URL_QUERY);
      parse_str($q, $values);
      $stat = _simplenews_statistics_decode($values);
      $query = 'UPDATE {shorturl_link} SET orig_url="%s" WHERE lid=%d';
      db_query($query, $stat['url'], $row['lid']);
      $n++;
    }
    watchdog('simplenews_statistics', "Decrypted {$n} old URLs in table shorturl_link.");
  }
}

Functions

Namesort descending Description
simplenews_statistics_access Access for newsletter statistics.
simplenews_statistics_click Gathers the clicks.
simplenews_statistics_cron Implements hook_cron().
simplenews_statistics_help Implements hook_help().
simplenews_statistics_mail_alter Implements hook_mail_alter().
simplenews_statistics_menu Implements hook_menu().
simplenews_statistics_nodeapi Implements hook_nodeapi().
simplenews_statistics_perm Implements hook_perm().
simplenews_statistics_view Gathers the opens.
simplenews_statistics_views_api Implements hook_views_api().
_simplenews_statistics_add_ga Add Google Analytics codes to a raw encoded/encrypted URL.
_simplenews_statistics_click_add Register click.
_simplenews_statistics_decode Decode a request.
_simplenews_statistics_decode_parameter Decode encode string so we can work with the data
_simplenews_statistics_encode_parameter Encode parameter so clicks and opens can not be faked
_simplenews_statistics_image_tag Add hidden image for view statistics.
_simplenews_statistics_open_add Add open to total.
_simplenews_statistics_parse_links Parse links in the body.
_simplenews_statistics_replace_url Alter link to go through statistics.