You are here

search_log.module in Search Log 6

Same filename and directory in other branches
  1. 8 search_log.module
  2. 7 search_log.module

Replaces default report of top search phrases.

File

search_log.module
View source
<?php

/**
 * @file
 *  Replaces default report of top search phrases.
 */
define('SEARCH_LOG_BLOCK', 0);
define('SEARCH_LOG_BLOCK_CACHE', 'search_log_block');
define('SEARCH_LOG_BLOCK_CACHE_TABLE', 'cache_block');
define('SEARCH_LOG_STATUS_ALL', 0);
define('SEARCH_LOG_STATUS_SUCCESS', 1);
define('SEARCH_LOG_STATUS_FAILED', 2);
define('SEARCH_LOG_TERMS_LOWERCASE', 0);
define('SEARCH_LOG_TERMS_UPPERCASE_FIRST', 1);
define('SEARCH_LOG_TERMS_UPPERCASE_WORDS', 2);

/**
 * Implementation of hook_menu().
 */
function search_log_menu() {
  $items = array();
  $items['admin/reports/search'] = array(
    'title' => 'Top search terms',
    'description' => 'View most popular search terms.',
    'page callback' => 'search_log_report',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'administer search',
    ),
    'file' => 'search_log.admin.inc',
  );

  // Add settings as task under the default search menu.
  $items['admin/settings/search/default'] = array(
    'title' => 'Search',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/settings/search/search_log'] = array(
    'title' => 'Search log',
    'description' => 'Setting for storing search results in the log.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'search_log_admin_settings',
    ),
    'access arguments' => array(
      'administer search',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'search_log.admin.inc',
  );
  $items['admin/settings/search/search_log/clear'] = array(
    'title' => 'Search log',
    'description' => 'Clear the search log.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'search_log_confirm_truncate',
    ),
    'access arguments' => array(
      'administer search',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'search_log.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_theme().
 */
function search_log_theme() {
  return array(
    'search_log_block' => array(
      'arguments' => array(
        'items' => NULL,
      ),
    ),
    'search_log_report' => array(
      'file' => 'search_log.admin.inc',
      'arguments' => array(
        'table' => NULL,
        'pager' => NULL,
        'summary' => NULL,
        'filters' => NULL,
      ),
    ),
    'search_log_summary' => array(
      'file' => 'search_log.admin.inc',
      'arguments' => array(
        'total' => 0,
        'unique' => 0,
        'failed' => 0,
      ),
    ),
  );
}

/**
 * Implementation of hook_form_alter().
 * 
 * Alter standard search forms to capture submission.
 */
function search_log_form_alter($form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'search_form':
    case 'search_block_form':
    case 'search_theme_form':
      if (isset($form['module'])) {
        if (!in_array($form['module']['#value'], variable_get('search_log_modules_enabled', array()))) {
          return;
        }
      }
      $form['#submit'][] = 'search_log_submit';
      break;
  }
  return $form;
}

/**
 * Process standard search forms to capture keys.
 */
function search_log_submit($form, &$form_state) {
  $keys = NULL;
  $module = isset($form_state['values']['module']) ? $form_state['values']['module'] : 'node';
  if (isset($form_state['values']['processed_keys'])) {
    $keys = $form_state['values']['processed_keys'];
  }
  elseif (isset($form_state['values']['search_theme_form'])) {
    $keys = $form_state['values']['search_theme_form'];
  }
  elseif (isset($form_state['values']['search_block_form'])) {
    $keys = $form_state['values']['search_block_form'];
  }
  if ($keys) {
    search_log($keys, $module);
  }
}

/**
 * Implementation of hook_cron().
 *
 * Expire outdated entries.
 */
function search_log_cron() {
  if ($days = (int) variable_get('search_log_cron', 0)) {

    // Get timestamp for 12:00 today UTC minus days.
    $day = mktime(0, 0, 0) - $days * 86400;
    db_query("DELETE FROM {search_log} WHERE day < %d", $day);
  }
  cache_clear_all(NULL, SEARCH_LOG_BLOCK_CACHE_TABLE);
}

/**
 * Store search keys, module and day.
 * 
 * Developers can call this function directly to add additional entries to the 
 * log or record failed searches (e.g. Lucene integration).
 */
function search_log($keys, $module, $counter = 1, $result = 0) {
  $keys = preg_replace("/\\s+/", ' ', trim($keys));
  $today = _search_log_get_time();

  // If search_log_preproces is enabled, the default is a failed search.
  if (!$result && variable_get('search_log_preprocess', FALSE)) {
    $result = -1;
  }
  if ($keys && $module) {

    // Normalize keys.
    switch (variable_get('search_log_terms', SEARCH_LOG_TERMS_LOWERCASE)) {
      case SEARCH_LOG_TERMS_LOWERCASE:
        $keys = drupal_strtolower($keys);
        break;
      case SEARCH_LOG_TERMS_UPPERCASE_FIRST:
        $keys = drupal_ucfirst($keys);
        break;
      case SEARCH_LOG_TERMS_UPPERCASE_WORDS:
        $keys = ucwords(drupal_strtolower($keys));
        break;
    }
    if ($qid = db_result(db_query("SELECT qid FROM {search_log} WHERE q='%s' AND module='%s' AND day=%d", $keys, $module, $today))) {
      db_query("UPDATE {search_log} SET counter = (counter + %d), result = %d WHERE qid = %d", $counter, $result, $qid);
    }
    else {
      db_query("INSERT INTO {search_log} (q, module, day, counter, result) VALUES ('%s', '%s', %d, %d, %d)", $keys, $module, $today, $counter, $result);
    }
  }
}

/**
 * Process search results.
 *
 * The $variables array contains the following arguments:
 * - $results
 * - $type
 *
 * Search does not have a hook to obtain the number of search results. This
 * theme function is called only if there are results. In addition, some 
 * modules may bypass this function if it implements the op search_page.
 * 
 * @see search_view(), search_data(), template_preprocess_search_results()
 */
function search_log_preprocess_search_results(&$variables) {
  if (!variable_get('search_log_preprocess', FALSE)) {
    return;
  }
  $keys = urldecode(arg(2));
  $module = $variables['type'];
  $today = _search_log_get_time();
  $result = count($variables['results']);
  if ($qid = db_result(db_query("SELECT qid FROM {search_log} WHERE q='%s' AND module='%s' AND day=%d", $keys, $module, $today))) {
    db_query("UPDATE {search_log} SET result = %d WHERE qid = %d", $result, $qid);
  }
}

/**
 * Implementation of hook_block().
 */
function search_log_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks[SEARCH_LOG_BLOCK]['info'] = t('Top Searches');
      return $blocks;
    case 'view':
      switch ($delta) {
        case SEARCH_LOG_BLOCK:
          if ($items = _search_log_items()) {
            $block['subject'] = t("Top Searches");
            $block['content'] = theme('search_log_block', $items);
          }
          break;
      }
      return $block;
    case 'configure':
      $form['search_log_block_max'] = array(
        '#type' => 'textfield',
        '#title' => t('Maximum number of search terms'),
        '#size' => 4,
        '#default_value' => variable_get('search_log_block_max', 10),
      );
      $form['search_log_block_count'] = array(
        '#type' => 'radios',
        '#title' => t('Display search count for terms?'),
        '#options' => array(
          t('No'),
          t('Yes'),
        ),
        '#default_value' => variable_get('search_log_block_count', 0),
      );
      $form['search_log_block_days'] = array(
        '#type' => 'textfield',
        '#title' => t('Number of days for search terms'),
        '#description' => t('Enter number of days for terms in Top Searches block. Enter 0 for all days.'),
        '#size' => 4,
        '#default_value' => variable_get('search_log_block_days', 0),
      );
      foreach (module_list() as $name) {
        if (module_hook($name, 'search') && ($title = module_invoke($name, 'search', 'name'))) {
          $module_options[$name] = $name;
        }
      }
      $form['search_log_block_modules'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Modules'),
        '#description' => t('Select modules to include in Top Searches block. If no modules are checked, all modules will be included.'),
        '#options' => $module_options,
        '#default_value' => variable_get('search_log_block_modules', array()),
      );
      $form['search_log_block_manual'] = array(
        '#type' => 'textarea',
        '#title' => t('Manually included terms'),
        '#description' => t('Enter term|module|count to be included in Top Searches block. For example, apple ipod|node|100 would link to search/node/apple+ipod.'),
        '#rows' => 5,
        '#default_value' => variable_get('search_log_block_manual', NULL),
      );
      return $form;
    case 'save':
      variable_set('search_log_block_max', $edit['search_log_block_max']);
      variable_set('search_log_block_count', $edit['search_log_block_count']);
      variable_set('search_log_block_days', $edit['search_log_block_days']);
      variable_set('search_log_block_modules', $edit['search_log_block_modules']);
      variable_set('search_log_block_manual', $edit['search_log_block_manual']);
      cache_clear_all(SEARCH_LOG_BLOCK_CACHE, 'cache', TRUE);
      break;
  }
}

/**
 * Theme function
 */
function theme_search_log_block($items) {
  $count_enabled = variable_get('search_log_block_count', 0);
  foreach ($items as $item) {
    if ($count_enabled) {
      $count = $item['count'] ? ' (' . $item['count'] . ')' : '';
    }
    $links[] = l($item['q'], 'search/' . $item['module'] . '/' . $item['q']) . '<span class="item-count">' . $count . '</span>';
  }
  return theme('item_list', $links);
}

/**
 * Internal function to generate cached top search terms.
 * 
 * Block cache is 1 hour and cleared on cron.
 */
function _search_log_items() {
  if ($cache = cache_get(SEARCH_LOG_BLOCK_CACHE, SEARCH_LOG_BLOCK_CACHE_TABLE)) {
    $items = unserialize($cache->data);
  }
  else {
    $max = variable_get('search_log_block_max', 10);
    $terms_manual = array();
    $terms = explode("\n", variable_get('search_log_block_manual', NULL));
    foreach ($terms as $term) {
      if ($term) {
        list($item['q'], $item['module'], $item['count']) = explode('|', $term);
        if (isset($item['q']) && isset($item['module'])) {
          $items[] = $item;
          $terms_manual[] = $item['q'];
          $max--;
        }
        unset($item);
      }
    }
    $query_where_days = '';
    if ($days = variable_get('search_log_block_days', 0)) {
      $today = _search_log_get_time();
      $from_time = $today - $days * 86400;
      $query_where_days = ' AND day >= ' . $from_time;
    }
    $modules = array_flip(variable_get('search_log_block_modules', array()));
    $query_where_modules = '';
    unset($modules[0]);
    if (!empty($modules)) {
      $query_where_modules = ' AND module IN ("' . implode('","', $modules) . '")';
    }
    if ($max > 0) {
      $query = db_query_range('SELECT q, module, SUM(counter) as count FROM {search_log} WHERE result >= 0' . $query_where_days . $query_where_modules . ' GROUP BY q, module ORDER BY count DESC', 0, $max);
      while ($max && ($item = db_fetch_array($query))) {
        if (!in_array($item['q'], $terms_manual)) {
          $items[] = $item;
          $max--;
        }
      }
    }
    uasort($items, '_search_log_items_sort');
    cache_set(SEARCH_LOG_BLOCK_CACHE, serialize($items), SEARCH_LOG_BLOCK_CACHE_TABLE, time() + 3600);
  }
  return $items;
}

/**
 * Utility time function.
 * 
 * Effectively returns time() rounded down to nearest day.
 */
function _search_log_get_time() {
  static $today;
  if (!isset($today)) {
    $today = mktime(0, 0, 0);
  }
  return $today;
}

/**
 * Utility sort function.
 */
function _search_log_items_sort($a, $b) {
  return $b['count'] > $a['count'] ? TRUE : FALSE;
}

Functions

Namesort descending Description
search_log Store search keys, module and day.
search_log_block Implementation of hook_block().
search_log_cron Implementation of hook_cron().
search_log_form_alter Implementation of hook_form_alter().
search_log_menu Implementation of hook_menu().
search_log_preprocess_search_results Process search results.
search_log_submit Process standard search forms to capture keys.
search_log_theme Implementation of hook_theme().
theme_search_log_block Theme function
_search_log_get_time Utility time function.
_search_log_items Internal function to generate cached top search terms.
_search_log_items_sort Utility sort function.

Constants