You are here

mostpopular.module in Drupal Most Popular 7

Same filename and directory in other branches
  1. 6 mostpopular.module

The main file for the Most Popular module.

File

mostpopular.module
View source
<?php

// $Id$

/*
 * Drupal Most Popular - Showcase the most popular content across your Drupal website and engage your audience.
 * Copyright © 2009-2012 New Signature
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * You can contact New Signature by electronic mail at labs@newsignature.com -or- by U.S. Postal Service at 1100 H St. NW, Suite 940, Washington, DC 20005.
 */

/**
 * @file
 * The main file for the Most Popular module.
 */

// Define some constants to determine how much to style the mostpopular block.
define('MOSTPOPULAR_STYLE_NONE', 0);
define('MOSTPOPULAR_STYLE_BASIC', 1);
define('MOSTPOPULAR_STYLE_FULL', 2);
define('MOSTPOPULAR_SERVICE_STATUS_ERROR', -2);
define('MOSTPOPULAR_SERVICE_STATUS_DISABLED', -1);
define('MOSTPOPULAR_SERVICE_STATUS_PENDING', 0);
define('MOSTPOPULAR_SERVICE_STATUS_CONFIGURED', 1);
define('MOSTPOPULAR_SERVICE_STATUS_OK', 2);
define('MOSTPOPULAR_DEFAULT_EXCLUDES', "admin/*\nuser/*\nnode/*/*\n*?*\n");

/**
 * Implements hook_permission().
 */
function mostpopular_permission() {
  return array(
    'administer mostpopular' => array(
      'title' => t('Administer Most Popular'),
    ),
  );
}

/**
* Implement hook_help().
*/
function mostpopular_help($path, $arg) {
  if ($path == 'admin/help#mostpopular') {
    return t('The Most Popular module retrieves a list of the most popular content on your site through Google Analytics or other services, and provides a block for displaying the most popular content to users.');
  }
  if ($path == 'admin/config/mostpopular/intervals') {
    return '<p>' . t("The interval field for each row must contain a string that can be understood by\n<a href='@strtotime' target='php'>strtotime()</a>.  You must specify each as a\nnegative interval relative to today.", array(
      '@strtotime' => 'http://php.net/manual/en/function.strtotime.php',
    )) . '</p>' . '<p>' . t('To remove an interval, clear both the title and interval values.') . '</p>' . '<p>' . t("If you make changes to the intervals, any custom service throttles you may\nhave set up will be reset to their default values.") . '</p>';
  }
}

/**
 * Implements hook_menu().
 */
function mostpopular_menu() {
  $items = array();
  $items['admin/config/mostpopular'] = array(
    'title' => 'Most Popular',
    'description' => 'Configure the Most Popular block functionality.',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array(
      'administer mostpopular',
    ),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
    'position' => 'right',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/mostpopular/config'] = array(
    'title' => 'Settings',
    'description' => 'Basic settings for all Most Popular blocks.',
    'page callback' => 'drupal_get_form',
    'file' => 'mostpopular.admin.inc',
    'page arguments' => array(
      'mostpopular_settings_form',
    ),
    'access arguments' => array(
      'administer mostpopular',
    ),
    'type' => MENU_LOCAL_TASK | MENU_VISIBLE_IN_TREE,
    'weight' => 0,
  );
  $items['admin/config/mostpopular/blocks'] = array(
    'title' => 'Blocks',
    'description' => 'Configure the available Most Popular blocks.',
    'file' => 'mostpopular.blocks.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mostpopular_blocks_admin_form',
    ),
    'access arguments' => array(
      'administer mostpopular',
    ),
    'type' => MENU_LOCAL_TASK | MENU_VISIBLE_IN_TREE,
    'weight' => 5,
  );
  $items['admin/config/mostpopular/services'] = array(
    'title' => 'Services',
    'description' => 'Configure the services available in each Most Popular block.',
    'file' => 'mostpopular.services.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mostpopular_services_admin_form',
    ),
    'access arguments' => array(
      'administer mostpopular',
    ),
    'type' => MENU_LOCAL_TASK | MENU_VISIBLE_IN_TREE,
    'weight' => 10,
  );
  $items['admin/config/mostpopular/services/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -50,
  );
  $items['admin/config/mostpopular/services/%/edit'] = array(
    'title callback' => 'mostpopular_service_title',
    'title arguments' => array(
      4,
    ),
    'file' => 'mostpopular.services.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mostpopular_service_config_form',
      4,
    ),
    'access arguments' => array(
      'administer mostpopular',
    ),
    'type' => MENU_NORMAL_ITEM,
    'context' => MENU_CONTEXT_INLINE | MENU_CONTEXT_PAGE,
  );
  $items['admin/config/mostpopular/services/%/delete'] = array(
    'title' => 'Delete',
    'file' => 'mostpopular.services.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mostpopular_service_delete_form',
      4,
    ),
    'access arguments' => array(
      'administer mostpopular',
    ),
    'type' => MENU_NORMAL_ITEM,
    'context' => MENU_CONTEXT_INLINE | MENU_CONTEXT_PAGE,
  );
  $items['admin/config/mostpopular/intervals'] = array(
    'title' => 'Intervals',
    'description' => 'Configure the intervals of time used for each Most Popular block.',
    'file' => 'mostpopular.intervals.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mostpopular_intervals_admin_form',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer mostpopular',
    ),
    'type' => MENU_LOCAL_TASK | MENU_VISIBLE_IN_TREE,
    'weight' => 20,
  );
  $items['admin/config/mostpopular/refresh'] = array(
    'title' => 'Refresh Stats',
    'description' => 'Refresh the Most Popular stats.',
    'page callback' => 'mostpopular_refresh',
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer mostpopular',
    ),
    'type' => MENU_LOCAL_TASK | MENU_VISIBLE_IN_TREE,
    'weight' => 30,
  );
  $items['mostpopular/ajax/%/%/%'] = array(
    'title' => 'Get the most popular stats via AJAX',
    'page callback' => 'mostpopular_items_ajax',
    'page arguments' => array(
      2,
      3,
      4,
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function mostpopular_theme($existing, $type, $theme, $path) {
  $themes = array(
    'mostpopular_admin_blocks_table' => array(
      'render element' => 'element',
      'file' => 'mostpopular.blocks.inc',
    ),
    'mostpopular_admin_services_table' => array(
      'render element' => 'element',
      'file' => 'mostpopular.services.inc',
    ),
    'mostpopular_service_status' => array(
      'variables' => array(
        'status' => 0,
      ),
      'file' => 'mostpopular.services.inc',
    ),
    'mostpopular_admin_intervals_table' => array(
      'render element' => 'element',
      'file' => 'mostpopular.intervals.inc',
    ),
    // Theme for the block
    'mostpopular_block' => array(
      'variables' => array(
        'services' => array(),
        'intervals' => array(),
        'bid' => 0,
      ),
      'file' => 'mostpopular.theme.inc',
      'template' => 'templates/mostpopular-block',
      'preprocess functions' => array(
        'template_preprocess',
        'template_preprocess_mostpopular_block',
      ),
    ),
    // Themes for the service tabs
    'mostpopular_services' => array(
      'variables' => array(
        'services' => array(),
      ),
      'file' => 'mostpopular.theme.inc',
      'preprocess functions' => array(
        'template_preprocess',
        'template_preprocess_mostpopular_services',
      ),
    ),
    'mostpopular_service' => array(
      'variables' => array(
        'service' => array(),
      ),
      'file' => 'mostpopular.theme.inc',
      'preprocess functions' => array(
        'template_preprocess',
        'template_preprocess_mostpopular_service',
      ),
    ),
    // Themes for the intervals tabs
    'mostpopular_intervals' => array(
      'variables' => array(
        'intervals' => array(),
      ),
      'file' => 'mostpopular.theme.inc',
      'preprocess functions' => array(
        'template_preprocess',
        'template_preprocess_mostpopular_intervals',
      ),
    ),
    'mostpopular_interval' => array(
      'variables' => array(
        'interval' => array(),
      ),
      'file' => 'mostpopular.theme.inc',
      'preprocess functions' => array(
        'template_preprocess',
        'template_preprocess_mostpopular_interval',
      ),
    ),
    // Themes for the most popular result items
    'mostpopular_items' => array(
      'variables' => array(
        'items' => array(),
      ),
      'file' => 'mostpopular.theme.inc',
      'preprocess functions' => array(
        'template_preprocess',
        'template_preprocess_mostpopular_items',
      ),
    ),
    'mostpopular_item' => array(
      'variables' => array(
        'item' => null,
      ),
      'file' => 'mostpopular.theme.inc',
      'template' => 'templates/mostpopular-item',
      'preprocess functions' => array(
        'template_preprocess',
        'template_preprocess_mostpopular_item',
      ),
    ),
    'mostpopular_items__none' => array(
      'variables' => array(),
      'file' => 'mostpopular.theme.inc',
    ),
  );
  return $themes;
}
function _mostpopular_save($type, &$object) {
  if (!is_object($object)) {
    $object = (object) $object;
  }

  // Get information about the table
  $table = "mostpopular_{$type}";
  $schema = drupal_get_schema($table);

  // Find the object's ID
  foreach ($schema['fields'] as $key => $field) {
    if ($field['type'] == 'serial') {
      $id = $key;
    }
    elseif (!empty($field['serialize'])) {
      $data = $key;
    }
  }

  // Create a data element if necessary
  if (isset($data) && !isset($object->{$data})) {
    $object->{$data} = array();
  }

  // Now, invoke the callbacks and save the object
  module_invoke_all("{$table}_presave", $object);
  if (!empty($object->{$id})) {
    drupal_write_record($table, $object, array(
      $id,
    ));
    $status = 'update';
  }
  else {
    drupal_write_record($table, $object);
    $status = 'insert';
  }
  module_invoke_all("{$table}_{$status}", $object);
}
function _mostpopular_callback($callback, $info) {
  if (is_array($info)) {
    $info = (object) $info;
  }
  if (isset($info->file)) {
    include $info->file;
  }
  $module = $info->module;
  $delta = $info->delta;
  $function = FALSE;
  if (isset($info->callbacks[$callback]) && function_exists($info->callbacks[$callback])) {
    $function = $info->callbacks[$callback];
  }
  elseif (function_exists("{$module}_{$callback}_{$delta}")) {
    $function = "{$module}_{$callback}_{$delta}";
  }
  elseif (function_exists("{$module}_{$callback}")) {
    $function = "{$module}_{$callback}";
  }
  elseif (function_exists("mostpopular_default_{$callback}")) {
    $function = "mostpopular_default_{$callback}";
  }
  return $function;
}
function _mostpopular_invoke($callback, $info, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL) {
  if (is_array($info)) {
    $info = (object) $info;
  }
  if (isset($info->file)) {
    include $info->file;
  }
  $function = _mostpopular_callback($callback, $info);
  if ($function) {
    return $function($info, $arg1, $arg2, $arg3);
  }
  return FALSE;
}

/**
 * Gets the list of available services.
 * 
 * @see hook_mostpopular_service_info()
 */
function mostpopular_service_info($module = NULL, $delta = NULL) {
  $services =& drupal_static(__FUNCTION__);
  if (!isset($services)) {
    $services = array();
    foreach (module_implements('mostpopular_service_info') as $m) {
      $info = module_invoke($m, 'mostpopular_service_info');
      foreach ($info as $d => $data) {
        $data += array(
          'module' => $m,
          'delta' => $d,
          'title' => '',
        );
        $services[$m][$d] = $data;
      }
    }
  }
  if (!empty($module)) {
    if (!empty($delta)) {
      if (isset($services[$module][$delta])) {
        return $services[$module][$delta];
      }
      return FALSE;
    }
    if (isset($services[$module])) {
      return $services[$module];
    }
    return FALSE;
  }
  return $services;
}

/**
 * Implements hook_load().
 * 
 * Loads a configured most popular service based on its ID.
 * 
 * @param integer $sid 
 *   The ID of the configured service.
 * @param boolean $reset 
 *   True if the cache should be reset.
 * @return object
 *   A most popular service configuration from the database.
 */
function mostpopular_service_load($sid, $reset = FALSE) {
  $services =& drupal_static(__FUNCTION__, array());
  if (!isset($services[$sid]) || $reset) {

    // Set variable so that it can be passed by reference
    $most = mostpopular_service_load_multiple(array(
      $sid,
    ));
    $services[$sid] = reset($most);
  }
  return $services[$sid];
}

/**
 * Loads configured most popular services within the given block.
 * 
 * @param integer $bid 
 *   The block ID.
 * @param boolean $enabled
 *   If true (default), only enabled services will be retrieved.
 *   If false, all services will be retrieved.
 * @return array 
 *   An array of most popular service configurations.
 */
function mostpopular_service_load_by_block($bid, $enabled = TRUE, $reset = FALSE) {
  $services =& drupal_static(__FUNCTION__, array());
  if (!isset($services[$bid]) || $reset) {
    $params = array(
      'bid' => $bid,
    );
    if ($enabled) {
      $params['enabled'] = TRUE;
    }
    $services[$bid] = mostpopular_service_load_multiple(array(), $params);
  }
  return $services[$bid];
}

/**
 * Implements hook_load_multiple().
 * 
 * Loads configured most popular services based on their IDs.
 * 
 * @param array $sids 
 *   The IDs of the desired services.
 * @param array $conditions
 *   Additional conditions to use.
 * @param boolean $reset 
 *   True if the cache should be reset.
 * @return array
 *   An array of most popular service configurations.
 *   
 * @see entity_load()
 */
function mostpopular_service_load_multiple($sids = array(), $conditions = array()) {

  // Unless conditions are specified explicitly, only get the enabled services.
  if (empty($sids) && empty($conditions)) {
    $conditions['enabled'] = 1;
  }
  $q = db_select('mostpopular_service', 's')
    ->fields('s');
  if (!empty($sids)) {
    $q
      ->condition('sid', $sids);
  }
  foreach ($conditions as $field => $value) {
    $q
      ->condition($field, $value);
  }
  $q
    ->orderBy('weight', 'ASC');
  $services = $q
    ->execute()
    ->fetchAllAssoc('sid', PDO::FETCH_ASSOC);

  // Augment each service with data from the hooks
  foreach ($services as $sid => $service) {
    $info = mostpopular_service_info($service['module'], $service['delta']);
    if ($info) {
      $service += $info;
    }

    // Unserialize the extra data
    if (!empty($service['data'])) {
      $service['data'] = unserialize($service['data']);
    }
    $services[$sid] = (object) $service;
  }
  return $services;
}
function mostpopular_service_save($service) {
  _mostpopular_save('service', $service);
}

/**
 * Before a mostpopular service is saved, update its status.
 * 
 * @param object $service The service being saved.
 */
function mostpopular_mostpopular_service_presave($service) {
  if ($service->enabled == 0) {
    $service->status = MOSTPOPULAR_SERVICE_STATUS_DISABLED;
  }
  elseif ($service->status == MOSTPOPULAR_SERVICE_STATUS_DISABLED) {
    $service->status = MOSTPOPULAR_SERVICE_STATUS_PENDING;
  }
}

/**
 * Returns the name of a service, for the menu hooks.
 * 
 * @param integer $sid The service ID.
 */
function mostpopular_service_title($sid) {
  $service = mostpopular_service_load($sid);
  if ($service) {
    return t('@name: %title', array(
      '@name' => $service->name,
      '%title' => $service->title,
    ));
  }
}

/**
 * Deletes the service configuration with the given ID.
 * 
 * @param integer $sid The service ID.
 */
function mostpopular_service_delete($sid) {
  if (!empty($sid) && is_numeric($sid)) {
    db_delete('mostpopular_service')
      ->condition('sid', $sid)
      ->execute();
  }
}

/**
 * Gets the configured Most Popular intervals.
 * 
 * @param integer $iid Optionally the ID of a single interval to retrieve.
 */
function mostpopular_intervals($iid = NULL) {
  $intervals =& drupal_static(__FUNCTION__);
  if (!isset($intervals)) {
    $intervals = db_select('mostpopular_interval', 'i')
      ->fields('i')
      ->execute()
      ->fetchAllAssoc('iid');
  }
  if (!empty($iid)) {
    if (isset($intervals[$iid])) {
      return $intervals[$iid];
    }
    return FALSE;
  }
  return $intervals;
}

/**
 * Gets the configured intervals for the given block.
 * 
 * @param integer $bid The ID of the block.
 */
function mostpopular_interval_load_by_block($bid) {
  $intervals =& drupal_static(__FUNCTION__, array());
  if (!isset($intervals[$bid])) {
    $intervals[$bid] = db_select('mostpopular_interval', 'i')
      ->fields('i')
      ->condition('bid', $bid)
      ->orderBy('weight', 'ASC')
      ->execute()
      ->fetchAllAssoc('iid');
  }
  return $intervals[$bid];
}

/**
 * Returns the timestamp, relative to the current time, that marks
 * the start of this interval.
 *
 * @param object $interval
 *   The interval configuration.
 * @return integer
 *   A timestamp in the past, or 0 if there is no interval string specified.
 */
function mostpopular_interval_timestamp($interval) {
  if (is_int($interval)) {
    $interval = mostpopular_intervals($interval);
  }
  return !empty($interval->string) ? strtotime($interval->string) : 0;
}

/**
 * Returns the full title, which is the interval's title prepended with
 * 'Past '. So, for instance, 'Day' becomes 'Past Day'.
 *
 * @return string
 *   The full title of the interval.
 */
function mostpopular_interval_title($interval) {
  if (is_int($interval)) {
    $interval = mostpopular_intervals($interval);
  }
  if (!$interval) {
    return '';
  }
  return 'Past ' . $interval->title;
}
function mostpopular_interval_save($interval) {
  _mostpopular_save('interval', $interval);
}
function mostpopular_interval_delete($interval) {

  // Delete the interval itself
  db_delete('mostpopular_interval')
    ->condition('iid', $interval->iid)
    ->execute();

  // Delete any cached items for this interval
  db_delete('mostpopular_item')
    ->condition('iid', $interval->iid)
    ->execute();
}

/**
 * Returns an array of default intervals to use for a new block.
 * 
 * @param integer $bid The ID of the block.
 */
function mostpopular_interval_defaults($bid) {
  $defaults = array(
    (object) array(
      'bid' => $bid,
      'title' => '1 day',
      'string' => '-1 day',
      'weight' => 0,
    ),
    (object) array(
      'bid' => $bid,
      'title' => '1 week',
      'string' => '-1 week',
      'weight' => 1,
    ),
    (object) array(
      'bid' => $bid,
      'title' => '1 month',
      'string' => '-1 month',
      'weight' => 2,
    ),
    (object) array(
      'bid' => $bid,
      'title' => '1 year',
      'string' => '-1 year',
      'weight' => 3,
    ),
  );

  // Allow other modules to change this list.
  drupal_alter('mostpopular_interval_defaults', $defaults, $bid);
  return $defaults;
}

/**
 * Gets the configured Most Popular blocks.
 * 
 * @param integer $bid Optionally, the block ID of an individual block to get.
 * @return mixed If a $bid was provided, returns just the matching block.  Otherwise,
 *   returns a list of all the configured blocks.
 */
function mostpopular_blocks($bid = NULL) {
  $blocks =& drupal_static(__FUNCTION__);
  if (!isset($blocks)) {
    $blocks = db_select('mostpopular_block', 'b')
      ->fields('b')
      ->execute()
      ->fetchAllAssoc('bid');
    foreach ($blocks as $b => $block) {
      $blocks[$b]->data = unserialize($block->data);
      if (!$blocks[$b]->data) {
        $blocks[$b]->data = array();
      }
    }
  }
  if (!empty($bid)) {
    if (isset($blocks[$bid])) {
      return $blocks[$bid];
    }
    return FALSE;
  }
  return $blocks;
}
function mostpopular_blocks_local() {
  $blocks =& drupal_static(__FUNCTION__);
  if (!isset($blocks)) {
    $blocks = db_select('mostpopular_block', 'b')
      ->fields('b')
      ->condition('remote_bid', NULL)
      ->execute()
      ->fetchAllAssoc('bid');
    foreach ($blocks as $b => $block) {
      $blocks[$b]->data = unserialize($block->data);
      if (!$blocks[$b]->data) {
        $blocks[$b]->data = array();
      }
    }
  }
  return $blocks;
}
function mostpopular_blocks_remote() {
  $blocks =& drupal_static(__FUNCTION__);
  if (!isset($blocks)) {
    $blocks = db_select('mostpopular_block', 'b')
      ->fields('b')
      ->condition('remote_bid', 0, '>')
      ->execute()
      ->fetchAllAssoc('bid');
    foreach ($blocks as $b => $block) {
      $blocks[$b]->data = unserialize($block->data);
      if (!$blocks[$b]->data) {
        $blocks[$b]->data = array();
      }
    }
  }
  return $blocks;
}

/**
 * Implements hook_block_save().
 * 
 * @param unknown_type $delta
 * @param unknown_type $values
 */
function mostpopular_block_save($block, $values = array()) {
  if (!is_object($block)) {
    $block = mostpopular_blocks($block);
  }
  if ($block) {
    if (isset($values['mostpopular'])) {
      $values = $values['mostpopular'];
    }
    foreach ($values as $key => $value) {
      if (is_array($value)) {
        $data =& $block->{$key};
        foreach ($value as $k => $v) {
          $data[$k] = $v;
        }
      }
      else {
        $block->{$key} = $value;
      }
    }
    _mostpopular_save('block', $block);
  }
}
function mostpopular_block_delete($bid) {
  if (empty($bid)) {
    return;
  }
  $services = mostpopular_service_load_by_block($bid, FALSE);
  $intervals = mostpopular_interval_load_by_block($bid);
  db_delete('mostpopular_block')
    ->condition('bid', $bid)
    ->execute();
  db_delete('mostpopular_service')
    ->condition('bid', $bid)
    ->execute();
  db_delete('mostpopular_interval')
    ->condition('bid', $bid)
    ->execute();
  if (!empty($services) && !empty($intervals)) {
    db_delete('mostpopular_item')
      ->condition('sid', array_keys($services))
      ->condition('iid', array_keys($intervals))
      ->execute();
    db_delete('mostpopular_last_run')
      ->condition('sid', array_keys($services))
      ->condition('iid', array_keys($intervals))
      ->execute();
  }
  drupal_set_message(t('Deleted block @bid', array(
    '@bid' => $bid,
  )));
}

/**
 * Implements hook_block_info().
 * 
 * Defines each of the most popular blocks.
 *
 * Each block will render its contents based on user cookies.  This allows us to 
 * cache the block itself.
 */
function mostpopular_block_info() {
  $out = array();
  $blocks = mostpopular_blocks();
  foreach ($blocks as $bid => $block) {
    $out[$block->bid] = array(
      'info' => t('Most Popular: @name', array(
        '@name' => !empty($block->name) ? $block->name : $block->bid,
      )),
      'cache' => DRUPAL_CACHE_GLOBAL,
    );
  }
  return $out;
}

/**
 * Implements hook_block_view().
 * 
 * Creates the most popular block.
 * 
 * The service and interval to show by default are loaded from the user's cookie.
 * If they don't have a cookie set, the first service and first interval will be
 * selected by default.
 * 
 * @param integer $bid The ID of the block.
 */
function mostpopular_block_view($bid = '') {
  $block = mostpopular_blocks($bid);
  if ($block) {
    if (!empty($block->data['remote_database'])) {
      if (!db_set_active($block->data['remote_database'])) {
        watchdog('mostpopular', 'Missing remote database @database', array(
          '@database' => $block->data['remote_database'],
        ), WATCHDOG_ERROR);
        db_set_active('default');
        return NULL;
      }
      $services = mostpopular_service_load_by_block($block->remote_bid);
      $intervals = mostpopular_interval_load_by_block($block->remote_bid);
      db_set_active('default');
    }
    else {
      $services = mostpopular_service_load_by_block($bid);
      $intervals = mostpopular_interval_load_by_block($bid);
    }
    $out = array(
      'subject' => !empty($block->title) ? $block->title : t('Most Popular'),
      'content' => array(
        '#theme' => 'mostpopular_block',
        '#services' => $services,
        '#intervals' => $intervals,
        '#bid' => $bid,
      ),
    );

    // Attach the stylesheets and javascript
    $path = drupal_get_path('module', 'mostpopular');
    switch (variable_get('mostpopular_styling', MOSTPOPULAR_STYLE_FULL)) {
      case MOSTPOPULAR_STYLE_BASIC:
        $out['content']['#attached']['css'][] = "{$path}/css/mostpopular-basic.css";
        break;
      case MOSTPOPULAR_STYLE_FULL:
        $out['content']['#attached']['css'][] = "{$path}/css/mostpopular-basic.css";
        $out['content']['#attached']['css'][] = "{$path}/css/mostpopular-full.css";
        break;
    }
    $out['content']['#attached']['js'][] = 'misc/jquery.cookie.js';
    $out['content']['#attached']['js'][] = "{$path}/js/fade.js";
    $out['content']['#attached']['js'][] = "{$path}/js/mostpopular.js";
    $out['content']['#attached']['js'][] = array(
      'data' => array(
        'mostpopular' => array(
          'url' => url('mostpopular/ajax'),
        ),
      ),
      'type' => 'setting',
    );
    return $out;
  }
  return NULL;
}
function mostpopular_items_ajax($bid, $sid, $iid) {
  $rendered =& drupal_static(__FUNCTION__);
  $block = mostpopular_blocks($bid);
  $cid = "mostpopular_items:{$bid}:{$sid}:{$iid}";
  if (!isset($rendered[$bid][$sid][$iid])) {
    $cached = cache_get($cid, 'cache_block');
    if ($cached && $cached->expire > time()) {
      $rendered[$bid][$sid][$iid] = $cached->data;
    }
  }
  if (!isset($rendered[$bid][$sid][$iid])) {
    if (!empty($block->data['remote_database'])) {
      if (!db_set_active($block->data['remote_database'])) {
        watchdog('mostpopular', 'Missing remote database @database', array(
          '@database' => $block->data['remote_database'],
        ), WATCHDOG_ERROR);
        db_set_active('default');
        drupal_json_output('');
        return;
      }
    }
    try {
      $items = db_select('mostpopular_item', 'i')
        ->fields('i')
        ->condition('sid', $sid)
        ->condition('iid', $iid)
        ->orderBy('count', 'DESC')
        ->execute()
        ->fetchAll();
      $out = array(
        '#theme' => 'mostpopular_items',
        '#service' => mostpopular_service_load($sid),
        '#interval' => mostpopular_intervals($iid),
        '#items' => $items,
      );

      // Cache the rendered items until the next update
      $next_run = db_select('mostpopular_last_run', 'm')
        ->fields('m', array(
        'next_run',
      ))
        ->condition('sid', $sid)
        ->condition('iid', $iid)
        ->execute()
        ->fetchColumn();
    } catch (Exception $ex) {
      watchdog('mostpopular', 'Failed to load @source mostpopular items: %message', array(
        '@source' => !empty($block->data['remote_database']) ? t('remote') : t('local'),
        '%message' => $ex
          ->getMessage(),
      ), WATCHDOG_WARNING);
    }
    db_set_active('default');
    $rendered[$bid][$sid][$iid] = drupal_render($out);
    cache_set($cid, $rendered[$bid][$sid][$iid], 'cache_block', $next_run > 0 ? $next_run : CACHE_TEMPORARY);
  }
  if (isset($rendered[$bid][$sid][$iid])) {
    drupal_json_output($rendered[$bid][$sid][$iid]);
  }
  else {
    drupal_json_output('');
  }
}

/**
 * Implements the default 'next_run' callback, for services that don't provide their own.
 *
 * Returns the timestamp at which to next refresh the data for this interval.
 *
 * @param object $service The service definition
 * @param integer $span The number of seconds representing the current interval.
 * @param integer $last_run The timestamp at which this service was last run for this interval.
 */
function mostpopular_default_next_run($service, $span, $last_run) {

  // If the interval is 2 days or less, refresh once per hour
  if ($span <= 60 * 60 * 24 * 2) {
    return strtotime('1 hour', $last_run);
  }
  elseif ($span >= 60 * 60 * 24 * 365) {
    return strtotime('1 week', $last_run);
  }

  // Otherwise, refresh once per day.
  return strtotime('1 day', $last_run);
}

/* ----------------------------------------------------------------------------
 * Cron jobs to fetch stats
 * --------------------------------------------------------------------------*/

/**
 * Implements hook_cron().
 *
 * Refreshes data from the services periodically.
 */
function mostpopular_cron() {
  $t = mostpopular_refresh(TRUE);
  watchdog('mostpopular_cron', $t);
}

/**
 * Refreshes data from each service by invoking the refresh callback for each service.
 */
function mostpopular_refresh($cron = FALSE) {
  $t = '';

  // Loop through all the local blocks
  $blocks = mostpopular_blocks_local();
  foreach ($blocks as $bid => $block) {
    $services = mostpopular_service_load_by_block($bid);
    $intervals = mostpopular_interval_load_by_block($bid);
    foreach ($services as $sid => $service) {
      $count = 0;
      $t .= '<div>';
      $t .= t("Refreshing %title", array(
        '%title' => $service->title,
      ));
      $status = array();
      foreach ($intervals as $iid => $interval) {

        // Get the number of seconds that this interval spans.
        $now = time();
        $span = $now - strtotime($interval->string, $now);

        // Get the next time this service should run.
        $row = db_select('mostpopular_last_run', 'r')
          ->fields('r', array(
          'last_run',
          'next_run',
        ))
          ->condition('sid', $sid)
          ->condition('iid', $iid)
          ->range(0, 1)
          ->execute()
          ->fetch();
        if ($row) {
          $last_run = $row->last_run;
          $next_run = $row->next_run;
        }
        else {
          $last_run = 0;
          $next_run = $now;
        }

        // When running as a cron job, ask the service how often it should refresh.
        // When running as a page request, refresh the service immediately.
        if (!$cron || $next_run <= $now) {

          // Invoke the module
          $out = _mostpopular_invoke('refresh', $service, $block, $span, $last_run);

          // If the module returned any results, save them to the database.
          if ($out !== FALSE) {
            if (count($out) > 0) {

              // Remove the previous results, if there are any
              db_delete('mostpopular_item')
                ->condition('sid', $sid)
                ->condition('iid', $iid)
                ->execute();

              // Write the new results to the cache table
              foreach ($out as $value) {
                $value->sid = $sid;
                $value->iid = $iid;

                // Fill in the entity properties if they are not already set.
                if (!empty($value->entity_type) && !empty($value->entity_id)) {
                  if (empty($value->path) || empty($value->title)) {
                    $entity = reset(entity_load($value->entity_type, array(
                      $value->entity_id,
                    )));
                    if ($entity && empty($value->path)) {
                      $uri = entity_uri($value->entity_type, $entity);
                      if (isset($uri['path'])) {
                        $value->path = $uri['path'];
                      }
                    }
                    if ($entity && empty($value->title)) {
                      $value->title = entity_label($value->entity_type, $entity);
                    }
                  }
                }

                // Save the URL as an absolute path, so we can reuse it on other sites.
                $value->url = url($value->path, array(
                  'absolute' => TRUE,
                ));
                drupal_write_record('mostpopular_item', $value);
              }

              // Since there were items returned, the service is OK.
              if ($service->status != MOSTPOPULAR_SERVICE_STATUS_OK) {
                $service->status = MOSTPOPULAR_SERVICE_STATUS_OK;
                mostpopular_service_save($service);
              }
            }

            // Ask the service when it should next run on this interval.
            $last_run = $now;
            $next_run = _mostpopular_invoke('next_run', $service, $span, $last_run);
            db_merge('mostpopular_last_run')
              ->fields(array(
              'last_run' => $last_run,
              'next_run' => $next_run,
            ))
              ->key(array(
              'sid' => $sid,
              'iid' => $iid,
            ))
              ->execute();

            // Clear the item cache
            cache_clear_all("mostpopular_items:{$bid}:{$sid}:{$iid}", 'cache_block');
            $status[] = t("%interval: Found %count items", array(
              '%interval' => $interval->title,
              '%count' => count($out),
            ));
          }
          else {
            $status[] = t("%interval: Error retrieving results", array(
              '%interval' => $interval->title,
            ));
            $service->status = MOSTPOPULAR_SERVICE_STATUS_ERROR;
            mostpopular_service_save($service);
          }
        }
        else {
          $status[] = t('%interval: No need to refresh yet', array(
            '%interval' => $interval->title,
          ));
        }
      }
      $t .= theme('item_list', array(
        'items' => $status,
      ));
      $t .= '</div><br/>';
    }
  }
  if (empty($t)) {
    $t .= t("You must first enable services.  Go to !link", array(
      '!link' => l(t('the services tab'), 'admin/settings/mostpopular/services'),
    ));
  }
  return $t;
}

/**
 * Matches the given URL to a Drupal node, resolving aliases appropriately.
 * The homepage will never be included in this list.
 *
 * The URL can be an internal URL or it can start with one of the configured
 * Drupal base paths, which will be stripped from the URL before the alias is
 * resolved.
 *
 * If the URL corresponds to a node, an array will be returned with properties
 * of that node from the most popular service.
 *
 * @param string $url
 *   A URL to match.  This can be either an internal Drupal URL or it can start
 *   with one of the configured site basepaths.
 * @param integer $count
 *   The number of times this node appears.
 *
 * @return array
 *   If the url corresponds to an entity, returns an array containing:
 *     - entity_type: the type of entity.
 *     - entity_id: the ID of the entity.
 *     - title: the title of the entity, fetched from the entity itself.
 *     - url: the external URL of the entity.
 *     - path: the internal Drupal path of the entity.
 *     - count: the number of times the entity was referenced.
 *   Otherwise, returns NULL.
 */
function mostpopular_match_result_nodes($url, $count, $options = array()) {
  $options += array(
    'entities_only' => FALSE,
    'entity_types' => array(),
  );
  $url = trim($url);

  // Strip out the base path from the URL.
  $basepaths = variable_get('mostpopular_basepaths', array());
  foreach ($basepaths as $base) {
    if (stripos($url, $base) === 0) {
      $url = drupal_substr($url, drupal_strlen($base));
      break;
    }
  }

  // Strip off any leading slashes
  if (stripos($url, '/') === 0) {
    $url = drupal_substr($url, 1);
  }

  // If the URL points to an excluded path, ignore it.
  $excludepaths = variable_get('mostpopular_exclude_paths', '');
  if (empty($url) || drupal_match_path($url, $excludepaths)) {
    return NULL;
  }

  // Get the internal path for the URL alias.
  $path = drupal_get_normal_path($url);

  // If the URL points to an excluded path, ignore it.
  if (drupal_match_path($path, $excludepaths)) {
    return NULL;
  }
  $out = (object) array(
    'path' => $path,
    'count' => $count,
  );

  // Attempt to lookup the entity
  $item = menu_get_item($path);
  $entity = NULL;
  if (!empty($item['load_functions'])) {
    foreach ($item['load_functions'] as $i => $func) {
      if ($func == 'menu_tail_load') {
        break;
      }

      // Extract the entity type from the name of the load function
      $entity_type = substr($func, 0, -5);

      // Compare this to the list of valid entity types
      if (empty($options['entity_types']) || isset($options['entity_types'][$entity_type])) {

        // Load the entity
        $parts = explode('/', $path);
        if (isset($parts[$i]) && function_exists($func)) {
          $entity = $func($parts[$i]);

          // Check that the bundle matches
          if (isset($entity) && is_object($entity)) {
            list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
            if (!empty($options['entity_types']) && !isset($options['entity_types'][$entity_type][$bundle])) {
              $entity = NULL;
            }
          }
          else {
            $entity = NULL;
          }
        }
      }
      break;
    }
  }
  if (isset($entity)) {

    // Check that anonymous users have access to view this entity
    $access = entity_access('view', $entity_type, $entity, user_load(0));
    if (isset($access) && $access === FALSE) {
      return NULL;
    }
    $out->entity_type = $entity_type;
    $out->entity_id = entity_id($entity_type, $entity);
    $out->title = entity_label($entity_type, $entity);
    $uri = entity_uri($entity_type, $entity);
    if (isset($uri['path'])) {
      $out->path = $uri['path'];
    }
  }
  if ($entity || !$options['entities_only']) {
    return $out;
  }
  return NULL;
}

/**
 * Implements hook_hook_info().
 */
function mostpopular_hook_info() {
  $hooks = array();
  $hooks['form_block_admin_configure_alter']['group'] = 'blocks';
  return $hooks;
}

Functions

Namesort descending Description
mostpopular_blocks Gets the configured Most Popular blocks.
mostpopular_blocks_local
mostpopular_blocks_remote
mostpopular_block_delete
mostpopular_block_info Implements hook_block_info().
mostpopular_block_save Implements hook_block_save().
mostpopular_block_view Implements hook_block_view().
mostpopular_cron Implements hook_cron().
mostpopular_default_next_run Implements the default 'next_run' callback, for services that don't provide their own.
mostpopular_help Implement hook_help().
mostpopular_hook_info Implements hook_hook_info().
mostpopular_intervals Gets the configured Most Popular intervals.
mostpopular_interval_defaults Returns an array of default intervals to use for a new block.
mostpopular_interval_delete
mostpopular_interval_load_by_block Gets the configured intervals for the given block.
mostpopular_interval_save
mostpopular_interval_timestamp Returns the timestamp, relative to the current time, that marks the start of this interval.
mostpopular_interval_title Returns the full title, which is the interval's title prepended with 'Past '. So, for instance, 'Day' becomes 'Past Day'.
mostpopular_items_ajax
mostpopular_match_result_nodes Matches the given URL to a Drupal node, resolving aliases appropriately. The homepage will never be included in this list.
mostpopular_menu Implements hook_menu().
mostpopular_mostpopular_service_presave Before a mostpopular service is saved, update its status.
mostpopular_permission Implements hook_permission().
mostpopular_refresh Refreshes data from each service by invoking the refresh callback for each service.
mostpopular_service_delete Deletes the service configuration with the given ID.
mostpopular_service_info Gets the list of available services.
mostpopular_service_load Implements hook_load().
mostpopular_service_load_by_block Loads configured most popular services within the given block.
mostpopular_service_load_multiple Implements hook_load_multiple().
mostpopular_service_save
mostpopular_service_title Returns the name of a service, for the menu hooks.
mostpopular_theme Implements hook_theme().
_mostpopular_callback
_mostpopular_invoke
_mostpopular_save

Constants