. * 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 '

' . t("The interval field for each row must contain a string that can be understood by strtotime(). You must specify each as a negative interval relative to today.", array( '@strtotime' => 'http://php.net/manual/en/function.strtotime.php', )) . '

' . '

' . t('To remove an interval, clear both the title and interval values.') . '

' . '

' . t( "If you make changes to the intervals, any custom service throttles you may have set up will be reset to their default values.") . '

'; } } /** * 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}"; } // If the module does not define the callback but there is a default, use it. 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) { $services[$sid] = reset(mostpopular_service_load_multiple(array($sid))); } 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); } // If the interval is 1 year or more, refresh once per week 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 .= '
'; $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 .= '

'; } } 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; }