You are here

views.module in Views (for Drupal 7) 5

Same filename and directory in other branches
  1. 8.3 views.module
  2. 6.3 views.module
  3. 6.2 views.module
  4. 7.3 views.module

File

views.module
View source
<?php

// ---------------------------------------------------------------------------
// Drupal Hooks

/**
 * Implementation of hook_help()
 */
function views_help($section) {
  switch ($section) {
    case 'admin/help#views':
      return t('The views module creates customized views of node lists. You may need to activate the Views UI module to get to the user administration pages.');
  }
}

/**
 * Implementation of hook_menu()
 */
function views_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    views_load_cache();

    // Invalidate the views cache to ensure that views data gets rebuilt.
    // This is the best way to tell that module configuration has changed.
    if (arg(0) == 'admin' && arg(2) == 'modules') {
      views_invalidate_cache();
    }
    views_menu_standard_items($items);
  }
  else {
    views_menu_inline_items($items);
  }
  return $items;
}

/**
 * Add the menu items for all non-inline views to the menu
 */
function views_menu_standard_items(&$items) {
  $result = db_query("SELECT * FROM {view_view} WHERE page = 1");
  while ($view = db_fetch_object($result)) {

    // This happens before the next check; even if it's put off for later
    // it is still used.
    $used[$view->name] = true;

    // Skip views with inline arguments.
    if ($view->url[0] == '$' || strpos($view->url, '/$') !== FALSE) {
      continue;
    }

    // unpack the array
    $view->access = $view->access ? explode(', ', $view->access) : array();
    _views_create_menu_item($items, $view, $view->url);
  }
  $default_views = _views_get_default_views();
  $views_status = variable_get('views_defaults', array());

  // Process default views
  foreach ($default_views as $name => $view) {
    if ($view->page && !$used[$name] && ($views_status[$name] == 'enabled' || !$view->disabled && $views_status[$name] != 'disabled')) {

      // skip views with inline args
      if ($view->url[0] == '$' || strpos($view->url, '/$') !== FALSE) {
        continue;
      }
      _views_create_menu_item($items, $view, $view->url);
    }
  }
}
function views_menu_inline_items(&$items) {

  // I don't think we gain anything by caching these, there should never
  // be all that many, and caching == a database hit.
  $tokens = module_invoke_all('views_url_tokens');
  $args = explode('/', $_GET['q']);
  $urls = views_get_all_urls();
  foreach ($urls as $view_name => $url) {
    if ($url[0] != '$' && strpos($url, '/$') === FALSE) {
      if (module_exists('views_ui') && user_access('administer views')) {
        $view_args = $args;
        foreach (explode('/', $url) as $num => $element) {
          if ($element != $args[$num]) {
            continue 2;
          }
          unset($view_args[$num]);
        }
        views_menu_admin_items($items, $view_name, $view_args, $args);
      }
    }
    else {

      // Do substitution on args.
      $use_view = $use_menu = FALSE;
      $menu_args = $view_args = $menu_path = array();
      foreach (explode('/', $url) as $num => $element) {
        if ($element[0] == '$') {

          // If we pass the token check, this view is definitely being used.
          list($token, $argument) = explode('-', $element);
          if ($tokens[$token] && function_exists($tokens[$token])) {
            if (!($use_view = $use_menu = $tokens[$token]($element, $argument, arg($num)))) {
              break;
            }
          }
          $menu_path[] = $view_args[] = arg($num);
        }
        else {
          unset($menu_args[$num]);
          $menu_path[] = $element;
          if ($element != arg($num)) {
            $use_menu = FALSE;
          }
        }

        // we are only using views that match our URL, up to the
        // point where we hit an inline arg.
        if (!$use_view && $element != arg($num)) {
          break;
        }
      }
      if ($use_view) {
        $path = implode('/', $menu_path);
        $view = views_get_view($view_name);
        $view->args = $view_args;
        _views_create_menu_item($items, $view, $path, MENU_CALLBACK, $view_args);
      }
      if (module_exists('views_ui') && user_access('administer views') && $use_menu) {
        views_menu_admin_items($items, $view_name, $menu_args, $args);
      }
    }
  }
}

/**
 * Implementation of hook_views_tabs().
 */
function views_views_tabs($op) {
  switch ($op) {
    case 'names':
      return array(
        'edit',
        'view',
        'clone',
        'export',
        'add',
      );
      break;
  }
}

/**
 * Add the adminstrative items to a view.
 */
function views_menu_admin_items(&$items, $view_name, $view_args, $args) {

  // Remove args that are tabs from $args.
  $tabs = array();
  foreach (module_implements('views_tabs') as $module) {
    $function = $module . '_views_tabs';
    $tabs = array_merge($tabs, (array) $function('names'));
  }

  // See what the last arg is.
  $last_arg = array_pop($args);
  if (in_array($last_arg, $tabs)) {
    array_pop($view_args);
  }
  else {
    $args[] = $last_arg;
  }
  $view = views_get_view($view_name);
  $path = implode('/', $args);
  views_ui_add_menu_items($items, $view, $path, $path != $_GET['q'] && !empty($view_args), $view_args);
}

/**
 * Load all of the URLs we use; this is cached in a special manner
 * in an attempt to make the menu system both flexible and yet not
 * overly intensive.
 */
function views_get_all_urls() {
  $cache = cache_get("views_urls", 'cache_views');
  if ($cache == 0) {
    $views = array();
    $used = array();

    // avoid creating empty path items by requiring an URL to be set
    $result = db_query("SELECT name, url FROM {view_view} WHERE page = 1 AND LENGTH(url) > 0");
    while ($view = db_fetch_object($result)) {
      $used[$view->name] = TRUE;
      $views[$view->name] = $view->url;
    }
    views_load_cache();
    $default_views = _views_get_default_views();
    $views_status = variable_get('views_defaults', array());
    foreach ($default_views as $name => $view) {
      if ($view->page && !$used[$name] && ($views_status[$name] == 'enabled' || !$view->disabled && $views_status[$name] != 'disabled')) {
        $views[$view->name] = $view->url;
      }
    }
    cache_set("views_urls", 'cache_views', serialize($views));
  }
  else {
    $views = unserialize($cache->data);
  }
  return $views;
}

/**
 * Helper function to add a menu item for a view.
 */
function _views_create_menu_item(&$items, $view, $path, $local_task_type = MENU_NORMAL_ITEM, $args = array()) {
  $title = filter_xss_admin(views_get_title($view, 'menu'));
  $type = _views_menu_type($view);
  if ($type == MENU_LOCAL_TASK || $type == MENU_DEFAULT_LOCAL_TASK) {
    $weight = $view->menu_tab_weight;
  }
  $access = views_access($view);
  $items[] = _views_menu_item($path, $title, $view, $args, $access, $type, $weight);
  if ($type == MENU_DEFAULT_LOCAL_TASK) {
    switch ($view->menu_tab_default_parent_type) {
      case 'tab':
        $parent_type = MENU_LOCAL_TASK;
        break;
      case 'normal':
        $parent_type = MENU_NORMAL_ITEM;
        break;
      case 'existing':
        $parent_type = 0;
        break;
    }
    if ($parent_type) {
      $title = filter_xss_admin(views_get_title($view, 'menu-parent'));
      $weight = $view->menu_parent_tab_weight;
      $items[] = _views_menu_item(dirname($path), $title, $view, $args, $access, $parent_type, $weight);
    }
  }
}

/**
 * Helper function to create a menu item for a view.
 */
function _views_menu_item($path, $title, $view, $args, $access, $type, $weight = NULL) {
  array_unshift($args, $view->name);
  $retval = array(
    'path' => $path,
    'title' => $title,
    'callback' => 'views_view_page',
    'callback arguments' => $args,
    'access' => user_access('access content') && $access,
    'type' => $type,
  );
  if ($weight !== NULL) {
    $retval['weight'] = $weight;
  }
  return $retval;
}

/**
 * Determine what menu type a view needs to use.
 */
function _views_menu_type($view) {
  if ($view->menu) {
    if ($view->menu_tab_default) {
      $type = MENU_DEFAULT_LOCAL_TASK;
    }
    else {
      if ($view->menu_tab) {
        $type = MENU_LOCAL_TASK;
      }
      else {
        $type = MENU_NORMAL_ITEM;
      }
    }
  }
  else {
    $type = MENU_CALLBACK;
  }
  return $type;
}

/**
 * Implementation of hook_perm
 */
function views_perm() {
  return array(
    'access all views',
  );
}

/**
 * Determine if the specified user has access to a view.
 */
function views_access($view, $account = NULL) {
  if (!$account) {
    global $user;
    $account = $user;
  }

  // Administrator privileges
  if (user_access('access all views', $account)) {
    return TRUE;
  }

  // All views with an empty access setting are available to all roles.
  if (!$view->access) {
    return TRUE;
  }

  // Otherwise, check roles
  static $roles = array();
  if (!isset($roles[$account->uid])) {
    $roles[$account->uid] = array_keys($account->roles);
    $roles[$account->uid][] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
  }
  return array_intersect($view->access, $roles[$account->uid]);
}

/**
 * Implementation of hook_block()
 */
function views_block($op = 'list', $delta = 0) {
  $block = array();
  if ($op == 'list') {
    views_load_cache();

    // Grab views from the database and provide them as blocks.
    $result = db_query("SELECT vid, block_title, page_title, name FROM {view_view} WHERE block = 1");
    while ($view = db_fetch_object($result)) {
      $block[$view->name]['info'] = filter_xss_admin(views_get_title($view, 'block-info'));
    }
    $default_views = _views_get_default_views();
    $views_status = variable_get('views_defaults', array());
    foreach ($default_views as $name => $view) {
      if (!isset($block[$name]) && $view->block && ($views_status[$name] == 'enabled' || !$view->disabled && $views_status[$name] != 'disabled')) {
        $title = filter_xss_admin(views_get_title($view, 'block'));
        $block[$name]['info'] = empty($title) ? $name : $title;
      }
    }
    return $block;
  }
  else {
    if ($op == 'view') {
      return views_view_block($delta);
    }
  }
}

// ---------------------------------------------------------------------------
// View Construction

/**
 * Ensure that all the arrays in a view exist so we don't run into array
 * operations on a non-array error.
 */
function _views_check_arrays(&$view) {
  $fields = array(
    'field',
    'sort',
    'argument',
    'filter',
    'exposed_filter',
    'access',
  );
  foreach ($fields as $field) {
    if (!is_array($view->{$field})) {
      $view->{$field} = array();
    }
  }
  return $view;
}

/**
 * This function loads a view by name or vid; if not found in db, it looks
 * for a default view by that name.
 */
function views_get_view($view_name) {
  views_load_cache();
  $view = _views_load_view($view_name);
  if ($view) {
    return $view;
  }
  if (is_int($view_name)) {
    return;

    // don't bother looking if view_name is an int!
  }
  $default_views = _views_get_default_views();
  if (isset($default_views[$view_name])) {
    $view = $default_views[$view_name];
    $view->is_cacheable = _views_is_cacheable($view);
    return $view;
  }
}

/**
 * This views a view by page, and should only be used as a callback.
 *
 * @param $view_name
 *   The name or id of the view to load
 * @param $args
 *   The arguments from the end of the url. Usually passed from the menu system.
 *
 * @return
 *   The view page.
 */
function views_view_page() {
  $args = func_get_args();
  $view_name = array_shift($args);
  $view = views_get_view($view_name);
  if (!$view) {
    drupal_not_found();
    exit;
  }
  $output = views_build_view('page', $view, $args, $view->use_pager, $view->nodes_per_page);
  if ($output === FALSE) {
    drupal_not_found();
    exit;
  }
  return $output;
}

/**
 * This views a view by block. Can be used as a callback or programmatically.
 */
function views_view_block($vid) {
  views_load_cache();
  $view = views_get_view($vid);
  if (!$view || !$view->block) {
    return NULL;
  }
  global $user;
  if (!$user->roles) {
    return NULL;
  }
  $roles = array_keys($user->roles);
  if (!views_access($view)) {
    return NULL;
  }
  $content = views_build_view('block', $view, array(), false, $view->nodes_per_block);
  if ($content) {
    $block['content'] = $content;
    $block['subject'] = filter_xss_admin(views_get_title($view, 'block'));
    return $block;
  }
  else {
    return NULL;
  }
}
function &views_set_current_view(&$view) {
  static $current_view = NULL;
  if ($view !== NULL) {
    unset($current_view);
    $current_view =& $view;
    unset($GLOBALS['current_view']);
    $GLOBALS['current_view'] =& $view;
  }
  return $current_view;
}
function &views_get_current_view() {
  $dummy = NULL;
  return views_set_current_view($dummy);
}

/**
 * This builds the basic view.
 * @param $type
 *    'page' -- Produce output as a page, sent through theme.
 *      The only real difference between this and block is that
 *      a page uses drupal_set_title to change the page title.
 *    'block' -- Produce output as a block, sent through theme.
 *    'embed' -- Use this if you want to embed a view onto another page,
 *      and don't want any block or page specific things to happen to it.
 *    'result' -- return an $info array. The array contains:
 *      query: The actual query ran.
 *      countquery: The count query that would be run if limiting was required.
 *      summary: True if an argument was missing and a summary was generated.
 *      level: What level the missing argument was at.
 *      result: Database object you can use db_fetch_object on.
 *    'items' -- return info array as above, except instead of result,
 *      items: An array of objects containing the results of the query.
 *    'queries' -- returns an array, summarizing the queries, but does not
 *      run them.
 * @param $view
 *   The actual view object. Use views_get_view() if you only have the name or
 *   vid.
 * @param $args
 *   args taken from the URL. Not relevant for many views. Can be null.
 * @param $use_pager
 *   If set, use a pager. Set this to the pager id you want it
 *   to use if you plan on using multiple pagers on a page. To go with the
 *   default setting, set to $view->use_pager. Note that the pager element
 *   id will be decremented in order to have the IDs start at 0.
 * @param $limit
 *   Required if $use_pager is set; if $limit is set and $use_pager is
 *   not, this will be the maximum number of records returned. This is ignored
 *   if using a view set to return a random result. To go with the default
 *   setting set to $view->nodes_per_page or $view->nodes_per_block. If
 *   $use_pager is set and this field is not, you'll get a SQL error. Don't
 *   do that!
 * @param $page
 *   $use_pager is false, and $limit is !0, $page tells it what page to start
 *   on, in case for some reason a particular section of view is needed,
 * @param $offset
 *   If $use_pager == false, skip the first $offset results. Does not work
 *   with pager.
 *   without paging on.
 * @param $filters
 *   An array of exposed filter ops and values to use with the exposed filter system
 *   Array has the form:
 *     [0] => array('op' => 'foo', 'value' => 'bar'),
 *     [1] => array('value' => 'zoo'), // for a locked operator, e.g.
 *   If no array is passed in, views will look in the $_GET array for potential filters
*/
function views_build_view($type, &$view, $args = array(), $use_pager = false, $limit = 0, $page = 0, $offset = 0, $filters = NULL) {
  views_load_cache();

  // Fix a number of annoying whines when NULL is passed in..
  if ($args == NULL) {
    $args = array();
  }

  // if no filter values are passed in, get them from the $_GET array
  if ($filters == NULL) {
    $filters = views_get_filter_values();
  }
  views_set_current_view($view);
  $view->build_type = $type;
  $view->type = $type == 'block' ? $view->block_type : $view->page_type;
  if ($view->view_args_php) {
    ob_start();
    $result = eval($view->view_args_php);
    if (is_array($result)) {
      $args = $result;
    }
    ob_end_clean();
  }
  $view->use_pager = $use_pager;
  $view->pager_limit = $limit;
  $view->current_page = $page;
  $view->offset = $offset;

  // Call a hook that'll let modules modify the view query before it is created
  foreach (module_implements('views_pre_query') as $module) {
    $function = $module . '_views_pre_query';
    $output .= $function($view);
  }
  $info = _views_get_query($view, $args, $filters);
  if ($info['fail']) {
    return FALSE;
  }
  if ($type == 'queries') {
    return $info;
  }
  $items = array();
  if ($info['query']) {
    $query = db_rewrite_sql($info['query'], 'node');
    if ($view->use_pager) {
      $cquery = db_rewrite_sql($info['countquery'], 'node', 'nid', $info['rewrite_args']);
      $result = pager_query($query, $view->pager_limit, $view->use_pager - 1, $cquery, $info['args']);
      $view->total_rows = $GLOBALS['pager_total_items'][$view->use_pager - 1];
    }
    else {
      $result = $view->pager_limit ? db_query_range($query, $info['args'], $view->current_page * $view->pager_limit + $view->offset, $view->pager_limit) : db_query($query, $info['args']);
    }
    $view->num_rows = db_num_rows($result);
    if ($type == 'result') {
      $info['result'] = $result;
      return $info;
    }
    while ($item = db_fetch_object($result)) {
      $items[] = $item;
    }
  }
  if ($type == 'items') {
    $info['items'] = $items;
    return $info;
  }

  // Call a hook that'll let modules modify the view just before it is displayed.
  foreach (module_implements('views_pre_view') as $module) {
    $function = $module . '_views_pre_view';
    $output .= $function($view, $items);
  }
  $view->real_url = views_get_url($view, $args);
  $output .= views_theme('views_view', $view, $type, $items, $info['level'], $args);

  // Call a hook that'll let modules modify the view just after it is displayed.
  foreach (module_implements('views_post_view') as $module) {
    $function = $module . '_views_post_view';
    $output .= $function($view, $items, $output);
  }
  return $output;
}

// ---------------------------------------------------------------------------
// Utility

/**
 * Load the cache sub-module
 */
function views_load_cache() {
  $path = drupal_get_path('module', 'views');
  require_once "./{$path}/views_cache.inc";
}

/**
 * Load the query sub-module
 */
function views_load_query() {
  $path = drupal_get_path('module', 'views');
  require_once "./{$path}/views_query.inc";
}

/**
 * Easily theme any item to a view.
 * @param $function
 *   The name of the function to call.
 * @param $view
 *   The view being themed.
 */
function views_theme() {
  $args = func_get_args();
  $function = array_shift($args);
  $view = $args[0];
  if (!($func = theme_get_function($function . "_" . $view->name))) {
    $func = theme_get_function($function);
  }
  if ($func) {
    return call_user_func_array($func, $args);
  }
}

/**
 * Easily theme any item to a field name.
 * field name will be in the format of TABLENAME_FIELDNAME
 * You have to understand a bit about the views data to utilize this.
 *
 * @param $function
 *   The name of the function to call.
 * @param $field_name
 *   The field being themed.
 */
function views_theme_field() {
  $args = func_get_args();
  $function = array_shift($args);
  $field_name = array_shift($args);
  $view = array_pop($args);
  if (!($func = theme_get_function($function . '_' . $view->name . '_' . $field_name))) {
    if (!($func = theme_get_function($function . '_' . $field_name))) {
      $func = theme_get_function($function);
    }
  }
  if ($func) {
    return call_user_func_array($func, $args);
  }
}

/**
 * Figure out what timezone we're in; needed for some date manipulations.
 */
function _views_get_timezone() {
  global $user;
  if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
    $timezone = $user->timezone;
  }
  else {
    $timezone = variable_get('date_default_timezone', 0);
  }

  // set up the database timezone
  if (in_array($GLOBALS['db_type'], array(
    'mysql',
    'mysqli',
  ))) {
    static $already_set = false;
    if (!$already_set) {
      if ($GLOBALS['db_type'] == 'mysqli' || version_compare(mysql_get_server_info(), '4.1.3', '>=')) {
        db_query("SET @@session.time_zone = '+00:00'");
      }
      $already_set = true;
    }
  }
  return $timezone;
}

/**
 * Figure out what the URL of the view we're currently looking at is.
 */
function views_get_url($view, $args) {
  $url = $view->url;
  if (!empty($url)) {
    $where = 1;
    foreach ($args as $arg) {

      // This odd construct prevents us from strposing once there is no
      // longer an $arg to replace.
      if ($where && ($where = strpos($url, '$arg'))) {
        $url = substr_replace($url, $arg, $where, 4);
      }
      else {
        $url .= "/{$arg}";
      }
    }
  }
  return $url;
}

/**
 * Figure out what the title of a view should be.
 */
function views_get_title($view, $context = 'menu', $args = NULL) {
  if ($context == 'menu-parent' && $view->menu_parent_title) {
    return $view->menu_parent_title;
  }
  if (($context == 'menu' || $context == 'menu-parent' || $context == 'admin') && $view->menu_title) {
    return $view->menu_title;
  }
  if ($context == 'block-info') {
    return $view->description ? $view->description : $view->name;
  }
  if ($args === NULL) {
    $args = $view->args;
  }

  // Grab the title from the highest argument we got. If there is no such
  // title, track back until we find a title.
  if (is_array($args)) {
    $rargs = array_reverse(array_keys($args));
    foreach ($rargs as $arg_id) {
      if ($title = $view->argument[$arg_id]['title']) {
        break;
      }
    }
  }
  if (!$title && ($context == 'menu' || $context == 'menu-parent' || $context == 'page' || $context == 'admin')) {
    $title = $view->page_title;
  }
  if (!$title && ($context == 'block' || $context == 'admin')) {
    $title = $view->block_title;
  }
  if (!$view->argument) {
    return $title;
  }
  views_load_cache();
  $arginfo = _views_get_arguments();
  foreach ($view->argument as $i => $arg) {
    if (!isset($args[$i])) {
      break;
    }
    $argtype = $arg['type'];
    if ($arg['wildcard'] == $args[$i] && $arg['wildcard_substitution'] != '') {
      $title = str_replace("%" . ($i + 1), $arg['wildcard_substitution'], $title);
    }
    else {
      if (function_exists($arginfo[$argtype]['handler'])) {

        // call the handler
        $rep = $arginfo[$argtype]['handler']('title', $args[$i], $argtype);
        $title = str_replace("%" . ($i + 1), $rep, $title);
      }
    }
  }
  return $title;
}

/**
 * Determine whether or not a view is cacheable. A view is not cacheable if
 * there is some kind of user input or data required. For example, views
 * that need to restrict to the 'current' user, or any views that require
 * arguments or allow click-sorting are not cacheable.
 */
function _views_is_cacheable(&$view) {

  // views with arguments are immediately not cacheable.
  if (!empty($view->argument) || !empty($view->exposed_filter) || !empty($view->no_cache)) {
    return false;
  }
  views_load_cache();
  $filters = _views_get_filters();
  foreach ($view->filter as $i => $filter) {
    if ($filters[$filter['field']]['cacheable'] == 'no') {
      return false;
    }
  }
  foreach ($view->field as $i => $field) {
    if ($field['sortable']) {
      return false;
    }
  }
  return true;
}

/**
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
 */
function views_invalidate_cache() {
  cache_clear_all('*', 'cache_views', true);
}

// ---------------------------------------------------------------------------
// Database functions

/**
 * Provide all the fields in a view.
 */
function _views_view_fields() {
  return array(
    'vid' => '%d',
    'name' => "'%s'",
    'description' => "'%s'",
    'access' => "'%s'",
    'page' => '%d',
    'page_title' => "'%s'",
    'page_header' => "'%s'",
    'page_header_format' => '%d',
    'page_footer' => "'%s'",
    'page_footer_format' => '%d',
    'page_empty' => "'%s'",
    'page_empty_format' => '%d',
    'page_type' => "'%s'",
    'use_pager' => '%d',
    'nodes_per_page' => '%d',
    'url' => "'%s'",
    'menu' => '%d',
    'menu_tab' => '%d',
    'menu_tab_default' => '%d',
    'menu_tab_weight' => '%d',
    'menu_title' => "'%s'",
    'menu_tab_default_parent_type' => "'%s'",
    'menu_parent_title' => "'%s'",
    'menu_parent_tab_weight' => '%d',
    'block' => '%d',
    'block_title' => "'%s'",
    'block_use_page_header' => '%d',
    'block_header' => "'%s'",
    'block_header_format' => '%d',
    'block_use_page_footer' => '%d',
    'block_footer' => "'%s'",
    'block_footer_format' => '%d',
    'block_use_page_empty' => '%d',
    'block_empty' => "'%s'",
    'block_empty_format' => '%d',
    'block_type' => "'%s'",
    'nodes_per_block' => '%d',
    'block_more' => '%d',
    'breadcrumb_no_home' => '%d',
    'changed' => '%d',
    'view_args_php' => "'%s'",
    'is_cacheable' => '%d',
  );
}

/**
 * Delete a view from the database.
 */
function _views_delete_view($view) {
  $view->vid = intval($view->vid);
  if (!$view->vid) {
    return;
  }
  db_query("DELETE FROM {view_view} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_sort} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_argument} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_tablefield} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_filter} where vid=%d", $view->vid);
  db_query("DELETE FROM {view_exposed_filter} where vid=%d", $view->vid);
  cache_clear_all('views_query:' . $view->name, 'cache_views');
  cache_clear_all();

  // In Drupal 5.0 and later this clears the page cache only.
}

/**
 * Load a view from the database -- public version of the function.
 */
function views_load_view($arg) {
  return _views_load_view($arg);
}

/**
 * Load a view from the database.
 * (deprecated; use views_load_view in favor of this function).
 */
function _views_load_view($arg) {
  static $cache = array();
  $which = is_numeric($arg) ? 'vid' : 'name';
  if (isset($cache[$which][$arg])) {
    return $cache[$which][$arg];
  }
  $where = is_numeric($arg) ? "v.vid =  %d" : "v.name = '%s'";
  $view = db_fetch_object(db_query("SELECT v.* FROM {view_view} v WHERE {$where}", $arg));
  if (!$view->name) {
    return NULL;
  }
  $view->access = $view->access ? explode(', ', $view->access) : array();

  // load the sorting criteria too.
  $result = db_query("SELECT * FROM {view_sort} vs WHERE vid = {$view->vid} ORDER BY position ASC");
  $view->sort = array();
  while ($sort = db_fetch_array($result)) {
    if (substr($sort['field'], 0, 2) == 'n.') {
      $sort['field'] = 'node' . substr($sort['field'], 1);
    }
    $sort['id'] = $sort['field'];
    $view->sort[] = $sort;
  }
  $result = db_query("SELECT * FROM {view_argument} WHERE vid = {$view->vid} ORDER BY position ASC");
  $view->argument = array();
  while ($arg = db_fetch_array($result)) {
    $arg['id'] = $arg['type'];
    $view->argument[] = $arg;
  }
  $result = db_query("SELECT * FROM {view_tablefield} WHERE vid = {$view->vid} ORDER BY position ASC");
  $view->field = array();
  while ($arg = db_fetch_array($result)) {
    if ($arg['tablename'] == 'n') {
      $arg['tablename'] = 'node';
    }
    $arg['id'] = $arg['fullname'] = "{$arg['tablename']}.{$arg['field']}";
    $arg['queryname'] = "{$arg['tablename']}_{$arg['field']}";
    $view->field[] = $arg;
  }
  $result = db_query("SELECT * FROM {view_filter} WHERE vid = {$view->vid} ORDER BY position ASC");
  views_load_cache();
  $filters = _views_get_filters();
  $view->filter = array();
  while ($filter = db_fetch_array($result)) {
    if (substr($filter['field'], 0, 2) == 'n.') {
      $filter['field'] = 'node' . substr($filter['field'], 1);
    }
    if ($filter['operator'] == 'AND' || $filter['operator'] == 'OR' || $filter['operator'] == 'NOR' || $filters[$filter['field']]['value-type'] == 'array') {
      if ($filter['value'] !== NULL && $filter['value'] !== '') {
        $filter['value'] = explode(',', $filter['value']);
      }
      else {
        $filter['value'] = array();
      }
    }
    $filter['id'] = $filter['field'];
    $view->filter[] = $filter;
  }
  $result = db_query("SELECT * FROM {view_exposed_filter} WHERE vid = {$view->vid} ORDER BY position ASC");
  $view->exposed_filter = array();
  while ($arg = db_fetch_array($result)) {
    $arg['id'] = $arg['field'];
    $view->exposed_filter[] = $arg;
  }
  $cache['vid'][$view->vid] = $view;
  $cache['name'][$view->name] = $view;
  return $view;
}

/**
 * Save a view to the database.
 */
function _views_save_view($view) {
  _views_check_arrays($view);
  $view->is_cacheable = _views_is_cacheable($view);
  $view->access = implode(', ', $view->access);
  $view->changed = time();
  $fields = _views_view_fields();
  if ($view->vid) {

    // update
    // Prepare the query:
    foreach ($view as $key => $value) {
      if (array_key_exists($key, $fields)) {
        $q[] = db_escape_string($key) . " = {$fields[$key]}";
        $v[] = $value;
      }
    }

    // Update the view in the database:
    db_query("UPDATE {view_view} SET " . implode(', ', $q) . " WHERE vid = '{$view->vid}'", $v);
    db_query("DELETE from {view_sort} WHERE vid='{$view->vid}'");
    db_query("DELETE from {view_argument} WHERE vid='{$view->vid}'");
    db_query("DELETE from {view_tablefield} WHERE vid='{$view->vid}'");
    db_query("DELETE from {view_filter} WHERE vid='{$view->vid}'");
    db_query("DELETE from {view_exposed_filter} WHERE vid='{$view->vid}'");
    cache_clear_all('views_query:' . $view->name, 'cache_views');
  }
  else {

    // insert
    // This method really saves on typos, and makes it a lot easier to add fields
    // later on.
    $view->vid = db_next_id('{view_view}_vid');

    // Prepare the query:
    foreach ($view as $key => $value) {
      if (array_key_exists((string) $key, $fields)) {
        $k[] = db_escape_string($key);
        $v[] = $value;
        $s[] = $fields[$key];
      }
    }
    db_query("INSERT INTO {view_view} (" . implode(", ", $k) . ") VALUES (" . implode(", ", $s) . ")", $v);
  }
  foreach ($view->sort as $i => $sort) {
    db_query("INSERT INTO {view_sort} (vid, position, field, sortorder, options) VALUES (%d, %d, '%s', '%s', '%s')", $view->vid, $i, $sort['field'], $sort['sortorder'], $sort['options']);
  }
  foreach ($view->argument as $i => $arg) {
    db_query("INSERT INTO {view_argument} (vid, type, argdefault, title, options, position, wildcard, wildcard_substitution) VALUES (%d, '%s', %d, '%s', '%s', %d, '%s', '%s')", $view->vid, $arg['type'], $arg['argdefault'], $arg['title'], $arg['options'], $i, $arg['wildcard'], $arg['wildcard_substitution']);
  }
  foreach ($view->field as $i => $arg) {
    db_query("INSERT INTO {view_tablefield} (vid, tablename, field, label, handler, sortable, defaultsort, options, position) VALUES (%d, '%s', '%s', '%s', '%s', %d, '%s', '%s', %d)", $view->vid, $arg['tablename'], $arg['field'], $arg['label'], $arg['handler'], $arg['sortable'], $arg['defaultsort'], $arg['options'], $i);
  }
  foreach ($view->filter as $i => $arg) {
    if (is_array($arg['value'])) {
      $arg['value'] = implode(',', $arg['value']);
    }
    db_query("INSERT INTO {view_filter} (vid, tablename, field, value, operator, options, position) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d)", $view->vid, $arg['tablename'], $arg['field'], $arg['value'], $arg['operator'], $arg['options'], $i);
  }
  foreach ($view->exposed_filter as $i => $arg) {
    db_query("INSERT INTO {view_exposed_filter} (vid, field, label, optional, is_default, single, operator, position) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d)", $view->vid, $arg['field'], $arg['label'], $arg['optional'], $arg['is_default'], $arg['single'], $arg['operator'], $i);
  }
  cache_clear_all('views_urls', 'cache_views');
  cache_clear_all();

  // clear the page cache as well.
  return $view->vid;
}

// ---------------------------------------------------------------------------
// Helper functions to build views and view data

/**
 * Helper function to make table creation a little easier. It adds the necessary
 * data to a $table array and returns it.
 */
function views_new_table($table_name, $provider, $left_table, $left_field, $right_field, $extra = NULL) {
  $table['name'] = $table_name;
  $table['provider'] = $provider;
  $table['join']['left']['table'] = $left_table;
  $table['join']['left']['field'] = $left_field;
  $table['join']['right']['field'] = $right_field;
  if ($extra) {
    $table['join']['extra'] = $extra;
  }
  return $table;
}

/**
 * Helper function to make table creation a little easier. It adds the necessary
 * data to the $table array.
 */
function views_table_add_field(&$table, $name, $label, $help, $others = array()) {
  views_table_add_data($table, 'fields', $name, $label, $help, $others);
}

/**
 * Helper function to make table creation a little easier. It adds the necessary
 * data to the $table array.
 */
function views_table_add_filter(&$table, $name, $label, $help, $others = array()) {
  views_table_add_data($table, 'filters', $name, $label, $help, $others);
}

/**
 * Helper function to make table creation a little easier. It adds the necessary
 * data to the $table array.
 */
function views_table_add_sort(&$table, $name, $label, $help, $others = array()) {
  views_table_add_data($table, 'sorts', $name, $label, $help, $others);
}

/**
 * Helper function to make table creation a little easier. It adds the necessary
 * data to the $table array.
 */
function views_table_add_data(&$table, $type, $name, $label, $help, $others = array()) {
  $table[$type][$name]['name'] = $label;
  $table[$type][$name]['help'] = $help;
  foreach ($others as $key => $value) {
    $table[$type][$name][$key] = $value;
  }
}

/**
 * Create a blank view.
 */
function views_create_view($name, $description, $access = array()) {
  $view = new stdClass();
  _views_check_arrays($view);
  $view->name = $name;
  $view->description = $description;
  $view->access = $access;

  // ensure some things are numerically 0.
  $view->nodes_per_page = 0;
  $view->nodes_per_block = 0;
  return $view;
}

/**
 * Add page info to a view.
 */
function views_view_add_page(&$view, $title, $url, $type, $pager, $nodes_per_page, $header, $header_format, $breadcrumb_no_home = FALSE) {
  $view->page = TRUE;
  $view->page_title = $title;
  $view->url = $url;
  $view->page_type = $type;
  $view->use_pager = $pager;
  $view->nodes_per_page = $nodes_per_page;
  $view->page_header = $header;
  $view->page_header_format = $header_format;
  $view->breadcrumb_no_home = $breadcrumb_no_home;
}

/**
 * Add menu info to a view.
 */
function views_view_add_menu(&$view, $title, $tab, $tab_weight, $default_tab) {
  $view->menu = TRUE;
  $view->menu_title = $title;
  $view->menu_tab = $tab;
  $view->menu_tab_weight = $tab_weight;
  $view->menu_tab_default = $default_tab;
}

/**
 * Add block info to a view.
 */
function views_view_add_block(&$view, $title, $type, $nodes_per_block, $more, $use_page_header, $header = '', $header_format = 0) {
  $view->block = TRUE;
  $view->block_title = $title;
  $view->block_type = $type;
  $view->nodes_per_block = $nodes_per_block;
  $view->block_more = $more;
  $view->block_use_page_header = $use_page_header;
  $view->block_header = $header;
  $view->block_header_format = $header_format;
}

/**
 * Add field info to a view.
 */
function views_view_add_field(&$view, $table, $field, $label, $sortable = FALSE, $default_sort = 0, $handler = '') {
  $view->field[] = array(
    'tablename' => $table,
    'field' => $field,
    'label' => $label,
    'sortable' => $sortable,
    'defaultsort' => $default_sort,
    'handler' => $handler,
  );
}

/**
 * Add argument info to a view.
 */
function views_view_add_argument(&$view, $type, $default, $title, $option = '') {
  $view->argument[] = array(
    'type' => $type,
    'argdefault' => $default,
    'title' => $title,
    'options' => $option,
  );
}

/**
 * Add filter info to a view.
 */
function views_view_add_filter(&$view, $table, $field, $operator, $value, $option) {
  $view->filter[] = array(
    'tablename' => $table,
    'field' => $field,
    'operator' => $operator,
    'value' => $value,
    'options' => $option,
  );
}

/**
 * Add exposed_filter info to a view.
 */
function views_view_add_exposed_filter(&$view, $table, $field, $optional, $is_default, $lock_operator, $single) {
  $view->exposed_filter[] = array(
    'tablename' => $table,
    'field' => $field,
    'optional' => $optional,
    'is_default' => $is_default,
    'operator' => $lock_operator,
    'single' => $single,
  );
}

/**
 * Add sort info to a view.
 */
function views_view_add_sort(&$view, $table, $field, $order, $option) {
  $view->sort[] = array(
    'tablename' => $table,
    'field' => $field,
    'sortorder' => $order,
    'options' => $option,
  );
}

// ---------------------------------------------------------------------------
// Themeable and support for themeables.

/**
 * Themeable function to handle displaying a specific field.
 */
function theme_views_handle_field($fields, $field, $data) {
  $info = $fields[$field['fullname']];
  if ($field['handler'] && function_exists($field['handler'])) {
    return $field['handler']($info, $field, $data->{$field}['queryname'], $data);
  }
  if ($info['handler'] && is_string($info['handler']) && function_exists($info['handler'])) {
    return $info['handler']($info, $field, $data->{$field}['queryname'], $data);
  }
  return check_plain($data->{$field}['queryname']);
}

/**
 * Construct a header for a table view.
 */
function _views_construct_header($view, $fields) {
  foreach ($view->field as $field) {
    $header = array();
    $info = $fields[$field['fullname']];
    if ($field['sortable']) {
      $header['data'] = $field['label'] ? $field['label'] : $info['name'];
      if (function_exists($info['sort_handler'])) {
        $header['field'] = $info['sort_handler']($field, $info);
      }
      else {
        $header['field'] = $field['queryname'];
      }
    }
    else {
      if ($field['label']) {
        $header['data'] = $field['label'];
      }
    }
    if ($field['defaultsort']) {
      $header['sort'] = strtolower($field['defaultsort']);
    }

    // Add CSS id to table cell header cell.
    $header['class'] = "view-cell-header" . views_css_safe(' view-field-' . $field['queryname']);
    $headers[] = $header;
  }
  return $headers;
}
function theme_views_display_filters($view) {
  return drupal_get_form("views_filters", $view);
}
function views_filters($view) {
  $form = _views_build_filters_form($view);
  $form['#view_name'] = $view->name;
  $form['#method'] = 'get';
  $form['#process'] = array(
    'views_filters_process' => array(),
  );
  $form['#action'] = url($view->real_url ? $view->real_url : $view->url, NULL, NULL, true);
  $form['view'] = array(
    '#type' => 'value',
    '#value' => $view,
  );
  $form['submit'] = array(
    '#type' => 'button',
    '#name' => '',
    '#value' => t('Submit'),
  );

  // clean URL get forms breaks if we don't give it a 'q'.
  if (!(bool) variable_get('clean_url', '0')) {
    $form['q'] = array(
      '#type' => 'hidden',
      '#value' => $view->real_url ? $view->real_url : $view->url,
      '#name' => 'q',
    );
  }
  return $form;
}
function _views_build_filters_form($view) {

  // When the form is retrieved through an AJAX callback, the cache hasn't
  // been loaded yet. The cache is necesssary for _views_get_filters().
  views_load_cache();
  $filters = _views_get_filters();
  foreach ($view->exposed_filter as $count => $expose) {
    $id = $expose['id'];
    $filterinfo = $filters[$id];
    foreach ($view->filter as $filter) {
      if ($filter['id'] == $id) {
        break;
      }
    }

    // set up the operator widget.
    if (!$expose['operator']) {

      // 'operator' is either an array or a handler
      $operator = $filterinfo['operator'];
      if (!is_array($operator) && function_exists($filterinfo['operator'])) {
        $operator = $filterinfo['operator']('operator', $filterinfo);
      }
      $form["op{$count}"] = array(
        '#type' => 'select',
        '#default_value' => $filter['operator'],
        '#options' => $operator,
      );
      if (array_key_exists("op{$count}", $_GET)) {
        $form["op{$count}"]["#default_value"] = $_GET["op{$count}"];
      }
    }

    // set up the filter widget.
    $item = $filterinfo['value'];
    if (!is_array($item['#options']) && function_exists($item['#options'])) {
      $item['#options'] = $item['#options']('value', $filterinfo);
    }
    if (!$expose['optional'] || $expose['is_default']) {
      $item['#default_value'] = $filter['value'];
    }
    if ($expose['single']) {
      unset($item['#multiple']);

      // On multi-select categories the #size element is in the form by default.  We remove it to allow the single-select drop-down filter to work.
      unset($item['#size']);
    }
    if ($expose['optional'] && is_array($item['#options'])) {
      $item['#options'] = array(
        '**ALL**' => t('<All>'),
      ) + $item['#options'];
    }
    if ($item['#multiple'] && is_array($item['#options'])) {
      $item['#size'] = min(count($item['#options']), 8);
    }
    if (array_key_exists("filter{$count}", $_GET)) {
      $item["#default_value"] = $_GET["filter{$count}"];
    }
    $form["filter{$count}"] = $item;
  }
  return $form;
}
function views_filters_process($form) {
  unset($form['form_id']);
  unset($form['form_token']);
  return $form;
}
function theme_views_filters($form) {
  $view = $form['view']['#value'];
  foreach ($view->exposed_filter as $count => $expose) {
    $row[] = drupal_render($form["op{$count}"]) . drupal_render($form["filter{$count}"]);
    $label[] = $expose['label'];
  }
  $row[] = drupal_render($form['submit']);
  $label[] = '';

  // so the column count is the same.
  // make the 'q' come first
  return drupal_render($form['q']) . theme('table', $label, array(
    $row,
  )) . drupal_render($form);
}

/**
 * Display the nodes of a view as a list.
 */
function theme_views_view_list($view, $nodes, $type) {
  $fields = _views_get_fields();
  foreach ($nodes as $node) {
    $item = '';
    foreach ($view->field as $field) {
      if (!isset($fields[$field['id']]['visible']) && $fields[$field['id']]['visible'] !== FALSE) {
        if ($field['label']) {
          $item .= "<div class='view-label " . views_css_safe('view-label-' . $field['queryname']) . "'>" . $field['label'] . "</div>";
        }
        $item .= "<div class='view-field " . views_css_safe('view-data-' . $field['queryname']) . "'>" . views_theme_field('views_handle_field', $field['queryname'], $fields, $field, $node, $view) . "</div>";
      }
    }
    $items[] = "<div class='view-item " . views_css_safe('view-item-' . $view->name) . "'>{$item}</div>\n";

    // l($node->title, "node/$node->nid");
  }
  if ($items) {
    return theme('item_list', $items);
  }
}

/**
 * Display the nodes of a view as a table.
 */
function theme_views_view_table($view, $nodes, $type) {
  $fields = _views_get_fields();
  foreach ($nodes as $node) {
    $row = array();
    foreach ($view->field as $field) {
      if ($fields[$field['id']]['visible'] !== FALSE) {
        $cell['data'] = views_theme_field('views_handle_field', $field['queryname'], $fields, $field, $node, $view);
        $cell['class'] = "view-field " . views_css_safe('view-field-' . $field['queryname']);
        $row[] = $cell;
      }
    }
    $rows[] = $row;
  }
  return theme('table', $view->table_header, $rows);
}

/**
 * Display the nodes of a view as teasers.
 */
function theme_views_view_teasers($view, $nodes, $type) {
  return views_theme('views_view_nodes', $view, $nodes, $type, true);
}

/**
 * Display the nodes of a view as plain nodes.
 */
function theme_views_view_nodes($view, $nodes, $type, $teasers = false, $links = true) {
  foreach ($nodes as $n) {
    $node = node_load($n->nid);
    $output .= node_view($node, $teasers, false, $links);
  }
  return $output;
}
function views_set_breadcrumb($view) {
  $breadcrumb = drupal_get_breadcrumb();
  if ($view->breadcrumb_no_home) {
    array_shift($breadcrumb);
  }
  if ($view->args) {

    // Add a breadcrumb trail for each level of argument we're at.
    $url = $view->url;
    $args = array();
    $where = 1;
    foreach ($view->args as $level => $arg) {
      if ($view->argument[$level]['argdefault'] != 1) {
        $breadcrumb[] = l(filter_xss_admin(views_get_title($view, 'page', $args)), $url, NULL, NULL, NULL, NULL, TRUE);

        // For next round.
      }
      $args[] = $arg;
      if ($where && ($where = strpos($url, '$arg'))) {
        $url = substr_replace($url, $arg, $where, 4);
      }
      else {
        $url .= "/{$arg}";
      }
    }
  }
  drupal_set_breadcrumb($breadcrumb);
}
function views_get_textarea($view, $type, $textarea) {
  $use_page = "block_use_page_{$textarea}";
  $var = ($type != 'block' || $view->{$use_page} ? 'page_' : 'block_') . $textarea;
  $format = $var . '_format';
  if ($view->{$var}) {
    return "<div class='" . views_css_safe('view-' . $textarea . ' view-' . $textarea . '-' . $view->name) . "'>" . check_markup($view->{$var}, $view->{$format}, false) . "</div>\n";
  }
}

/**
 * Prepare the specified string for use as a CSS identifier.
 */
function views_css_safe($string) {
  return str_replace('_', '-', $string);
}

/**
 * Display a view.
 */
function theme_views_view($view, $type, $nodes, $level = NULL, $args = NULL) {
  $num_nodes = count($nodes);
  if ($type == 'page') {
    drupal_set_title(filter_xss_admin(views_get_title($view, 'page')));
    views_set_breadcrumb($view);
  }
  if ($num_nodes) {
    $output .= views_get_textarea($view, $type, 'header');
  }
  if ($type != 'block' && $view->exposed_filter) {
    $output .= views_theme('views_display_filters', $view);
  }
  $plugins = _views_get_style_plugins();
  $view_type = $type == 'block' ? $view->block_type : $view->page_type;
  if ($num_nodes || $plugins[$view_type]['even_empty']) {
    if ($level !== NULL) {
      $output .= "<div class='view-summary " . views_css_safe('view-summary-' . $view->name) . "'>" . views_theme($plugins[$view_type]['summary_theme'], $view, $type, $level, $nodes, $args) . '</div>';
    }
    else {
      $output .= "<div class='view-content " . views_css_safe('view-content-' . $view->name) . "'>" . views_theme($plugins[$view_type]['theme'], $view, $nodes, $type) . '</div>';
    }
    $output .= views_get_textarea($view, $type, 'footer');
    if ($type == 'block' && $view->block_more && $num_nodes >= $view->nodes_per_block) {
      $output .= theme('views_more', $view->real_url);
    }
  }
  else {
    $output .= views_get_textarea($view, $type, 'empty');
  }
  if ($view->use_pager) {
    $output .= theme('pager', '', $view->pager_limit, $view->use_pager - 1);
  }
  if ($output) {
    $output = "<div class='view " . views_css_safe('view-' . $view->name) . "'>{$output}</div>\n";
  }
  return $output;
}

/**
 * Format the 'more' link for a view. Personally I prefer [More] but I've
 * been convinced to go with simply 'more'.
 */
function theme_views_more($url) {
  return "<div class='more-link'>" . l(t('more'), $url) . "</div>";
}

/**
 * Get the summary link for a view.
 */
function views_get_summary_link($argtype, $item, $base) {
  $arginfo = _views_get_arguments();
  return $arginfo[$argtype]['handler']('link', $item, $argtype, $base);
}

/**
 * Display a summary version of a view.
 */
function theme_views_summary($view, $type, $level, $nodes, $args) {
  foreach ($nodes as $node) {
    $items[] = views_get_summary_link($view->argument[$level]['type'], $node, $view->real_url) . " (" . $node->num_nodes . ")";
  }
  if ($items) {
    $output .= theme('item_list', $items);
  }
  return $output;
}

// ---------------------------------------------------------------------------
// Generic handlers. These make sense to be used in a lot of different places.

/**
 * Field handlers accept the following arguments:
 * @param $fieldinfo
 *   The array of info for that field from the global tables array.
 * @param $fielddata
 *   All of the info about that field in the database.
 * @param $value
 *   The value of the field fetched from the database.
 * @param $data
 *   The rest of the data about the node fetched from the database, in case
 *   the handler needs more than just the field.
 */

/**
 * Format a date.
 */
function views_handler_field_date($fieldinfo, $fielddata, $value, $data) {
  return $value ? format_date($value) : theme('views_nodate');
}

/**
 * Format a date using small representation.
 */
function views_handler_field_date_small($fieldinfo, $fielddata, $value, $data) {
  return $value ? format_date($value, 'small') : theme('views_nodate');
}

/**
 * Format a date using large representation.
 */
function views_handler_field_date_large($fieldinfo, $fielddata, $value, $data) {
  return $value ? format_date($value, 'large') : theme('views_nodate');
}

/**
 * Format a date using custom representation.
 */
function views_handler_field_date_custom($fieldinfo, $fielddata, $value, $data) {
  return $value ? format_date($value, 'custom', $fielddata['options']) : theme('views_nodate');
}

/**
 * Format a date as "X time ago".
 */
function views_handler_field_since($fieldinfo, $fielddata, $value, $data) {
  return $value ? t('%time ago', array(
    '%time' => format_interval(time() - $value, is_numeric($fielddata['options']) ? $fielddata['options'] : 2),
  )) : theme('views_nodate');
}
function theme_views_nodate() {
  return '<span class="views-nodate">' . t('never') . '</span>';
}

/**
 * Provide a list of all standard supproted date output handlers.
 */
function views_handler_field_dates() {
  return array(
    'views_handler_field_date_small' => t('As Short Date'),
    'views_handler_field_date' => t('As Medium Date'),
    'views_handler_field_date_large' => t('As Long Date'),
    'views_handler_field_date_custom' => t('As Custom Date'),
    'views_handler_field_since' => t('As Time Ago'),
  );
}
function views_handler_sort_date_options() {
  return array(
    '#type' => 'select',
    '#options' => array(
      'normal' => t('Normal'),
      'minute' => t('Granularity: minute'),
      'hour' => t('Granularity: hour'),
      'day' => t('Granularity: day'),
      'month' => t('Granularity: month'),
      'year' => t('Granularity: year'),
    ),
  );
}
function views_handler_sort_date($op, &$query, $sortinfo, $sort) {
  $timezone = _views_get_timezone();
  switch ($sort['options']) {
    case 'normal':
    default:
      $table = $sortinfo['table'];
      $field = $sortinfo['field'];
      break;
    case 'minute':
      $field = "DATE_FORMAT(FROM_UNIXTIME({$sortinfo['table']}.{$sortinfo['field']}), '%Y%m%%d%H%i')";
      break;
    case 'hour':
      $field = "DATE_FORMAT(FROM_UNIXTIME({$sortinfo['table']}.{$sortinfo['field']}), '%Y%m%%d%H')";
      break;
    case 'day':
      $field = "DATE_FORMAT(FROM_UNIXTIME({$sortinfo['table']}.{$sortinfo['field']}+{$timezone}), '%Y%m%%d')";
      break;
    case 'month':
      $field = "DATE_FORMAT(FROM_UNIXTIME({$sortinfo['table']}.{$sortinfo['field']}+{$timezone}), '%Y%m')";
      break;
    case 'year':
      $field = "DATE_FORMAT(FROM_UNIXTIME({$sortinfo['table']}.{$sortinfo['field']}+{$timezone}), '%Y')";
      break;
  }
  $alias = $as = $sortinfo['table'] . '_' . $sortinfo['field'];
  if (!$table) {
    $as .= '_orderby';
    $alias = $field;
  }

  //  $query->add_field($field, $table, $as);
  //  $query->orderby[] = "$alias $sort[sortorder]";
  $query
    ->add_orderby($table, $field, $sort['sortorder'], $as);
}
function views_handler_sort_date_minute($op, &$query, $sortinfo, $sort) {
  $field = "DATE_FORMAT(FROM_UNIXTIME({$table}.{$sortinfo['field']}), '%Y%m%%d%H%m')";
  $query
    ->add_orderby(NULL, $field, $sort['sortorder']);
}

/**
 * Format a field as an integer.
 */
function views_handler_field_int($fieldinfo, $fielddata, $value, $data) {
  return intval($value);
}

/**
 * Argument handlers take up to 4 fields, which vary based upon the operation.
 * @param $op
 *   The operation to perform:
 *   'summary': A summary view is being constructed. In this case the handler
 *              is to add the necessary components to the query to display
 *              the summary. It must return a $fieldinfo array with 'field'
 *              set to the field the summary is ordered by; if this is aliased
 *              for some reason (such as being an aggregate field) set 'fieldname'
 *              to the alias.
 *    'sort':   Set up the view to sort based upon the setting in $a2.
 *    'filter': Filter the view based upon the argument sent; essentially just
 *              add the where clause here.
 *    'link':   Provide a link from a summary view based upon the argument sent.
 *    'title':  Provide the title of a view for substitution.
 * @param &$query
 *   For summary, filter and link, this is the actual query object; for title this is
 *   simply the value of the argument.
 * @param $a2
 *   For summary, this is the type of the argument. For the others, this is the info
 *   for the argument from the global table. (Why is this not consistent? I dunno).
 * @param $a3
 *   For summary, this is the 'options' field from the db. For 'filter' this is
 *   the argument received. For 'link' this is the base URL of the link. Not used
 *   for 'title'.
 *
 */

// ---------------------------------------------------------------------------
// Filter handlers

/**
 * There are two kinds of filter handlers here; the easy kind simply creates an
 * array of options. For example, for taxonomy we provide a list of all taxonomy
 * terms which is placed in the select box.
 *
 * The other type is the 'custom' handler which is used to create a customized
 * WHERE clause for specialized filters.
 *
 * It takes 4 parameters.
 * @param $op
 *   At this time it will always be 'handler'.
 * @param $filter
 *   Information on the filter from the database, including 'options', 'value' and 'operator'.
 * @param $filterinfo
 *   Information on the filter from the global table array.
 * @param &$query
 *   The query object being worked on.
 */

/**
 * A list of and/or/nor.
 */
function views_handler_operator_andor() {
  return array(
    'AND' => t('Is All Of'),
    'OR' => t('Is One Of'),
    'NOR' => t('Is None Of'),
  );
}

/**
 * A list of or/nor.
 */
function views_handler_operator_or() {
  return array(
    'OR' => t('Is One Of'),
    'NOR' => t('Is None Of'),
  );
}

/**
 * A list of equal or not equal to.
 */
function views_handler_operator_eqneq() {
  return array(
    '=' => t('Is Equal To'),
    '!=' => t('Is Not Equal To'),
  );
}

/**
 * A list of greater / equal / less than
 */
function views_handler_operator_gtlt() {
  return array(
    '>' => t("Is Greater Than"),
    '>=' => t("Is Greater Than Or Equals"),
    '=' => t("Is Equal To"),
    '!=' => t("Is Not Equal To"),
    '<=' => t("Is Less Than Or Equals"),
    '<' => t("Is Less Than"),
  );
}

/**
 * A list of yes/no.
 */
function views_handler_operator_yesno() {
  return array(
    '1' => t('Yes'),
    '0' => t('No'),
  );
}

/*
 * Break x,y,z and x+y+z into an array. Numeric only.
 */
function _views_break_phrase($str) {
  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {

    // The '+' character in a query string may be parsed as ' '.
    return array(
      'or',
      preg_split('/[+ ]/', $str),
    );
  }
  else {
    if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
      return array(
        'and',
        explode(',', $str),
      );
    }
    else {
      return NULL;
    }
  }
}

/**
 * Default Views style plugins. Implementation of hook_views_style_plugins()
 */
function views_views_style_plugins() {
  return array(
    'list' => array(
      'name' => t('List View'),
      'theme' => 'views_view_list',
      'validate' => 'views_ui_plugin_validate_list',
      'needs_fields' => true,
      'weight' => -10,
    ),
    'table' => array(
      'name' => t('Table View'),
      'theme' => 'views_view_table',
      'validate' => 'views_ui_plugin_validate_table',
      'needs_fields' => true,
      'needs_table_header' => true,
      'weight' => -9,
    ),
    'teaser' => array(
      'name' => t('Teaser List'),
      'theme' => 'views_view_teasers',
      'weight' => -8,
    ),
    'node' => array(
      'name' => t('Full Nodes'),
      'theme' => 'views_view_nodes',
      'weight' => -7,
    ),
  );
}

/**
 * Default Views URL tokens
 */
function views_views_url_tokens() {
  return array(
    '$arg' => 'views_url_arg',
    '$node' => 'views_url_node',
    '$user' => 'views_url_user',
  );
}

/**
 * Handle '$arg' in a URL. Any non-empty value is true.
 */
function views_url_arg($token, $argument, $arg) {
  return $arg !== '' && $arg !== NULL;
}

/**
 * Handle '$node' in a URL. Any valid node wil ldo.
 */
function views_url_node($token, $argument, $arg) {
  if (!is_numeric($arg)) {
    return FALSE;
  }
  $node = node_load($arg);
  if (!$node) {
    return FALSE;
  }
  if ($argument && $node->type != $argument) {
    return FALSE;
  }

  // if a node loads, return true.
  return TRUE;
}

/**
 * Handle '$user' in a URL. Any valid user wil ldo.
 */
function views_url_user($token, $argument, $arg) {
  if (!is_numeric($arg)) {
    return FALSE;
  }

  // if a user loads, return true.
  return (bool) user_load($arg);
}

/**
 * A list of options to be used in LIKE queries
 */
function views_handler_operator_like() {
  return array(
    '=' => t('Is Equal To'),
    'contains' => t('Contains'),
    'word' => t('Contains Any Word'),
    'allwords' => t('Contains All Words'),
    'starts' => t('Starts With'),
    'ends' => t('Ends With'),
    'not' => t('Does Not Contain'),
  );
}

/**
 * Custom filter for LIKE operations
 */
function views_handler_filter_like($op, $filter, $filterinfo, &$query) {
  switch (trim($filter['value'])) {
    case '':
      return;
      break;
  }
  switch ($op) {
    case 'handler':
      $table = $filterinfo['table'];
      $column = $filterinfo['field'];
      $field = "{$table}.{$column}";
      $query
        ->ensure_table($table);
      switch ($filter['operator']) {
        case 'contains':
          $query
            ->add_where("UPPER(%s) LIKE UPPER('%%%s%%')", $field, $filter['value']);
          break;
        case 'word':
        case 'allwords':
          preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' ' . $filter['value'], $matches, PREG_SET_ORDER);
          foreach ($matches as $match) {
            $phrase = false;

            // Strip off phrase quotes
            if ($match[2][0] == '"') {
              $match[2] = substr($match[2], 1, -1);
              $phrase = true;
            }
            $words = trim($match[2], ',?!();:-');
            $words = $phrase ? array(
              $words,
            ) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
            foreach ($words as $word) {
              $where[] = "UPPER(%s) LIKE UPPER('%%%s%%')";
              $values[] = $field;
              $values[] = trim($word, " ,!?");
            }
          }
          if ($filter['operator'] == 'word') {
            $where = '(' . implode(' OR ', $where) . ')';
          }
          else {
            $where = implode(' AND ', $where);
          }

          // previously this was a call_user_func_array but that's unnecessary
          // as views will unpack an array that is a single arg.
          $query
            ->add_where($where, $values);
          break;
        case 'starts':
          $query
            ->add_where("UPPER(%s) LIKE UPPER('%s%%')", $field, $filter['value']);
          break;
        case 'ends':
          $query
            ->add_where("UPPER(%s) LIKE UPPER('%%%s')", $field, $filter['value']);
          break;
        case 'not':
          $query
            ->add_where("UPPER(%s) NOT LIKE UPPER('%%%s%%')", $field, $filter['value']);
          break;
        case '=':
          $query
            ->add_where("UPPER(%s) = UPPER('%s')", $field, $filter['value']);
          break;
      }
      break;
  }
}

/**
 * Custom filter for IS NULL and IS NOT NULL operations
 * Operator must be 'IS' or 'IS NOT'
 */
function views_handler_filter_null($op, $filter, $filterinfo, &$query) {
  switch ($op) {
    case 'handler':
      $table = $filterinfo['table'];
      $column = $filterinfo['field'];
      $field = "{$table}.{$column}";
      $query
        ->ensure_table($table);
      $operator = $filter['operator'];
      $query
        ->add_where("{$field} {$operator} NULL");
      break;
  }
}

/**
 * Format a field as file size.
 */
function views_handler_field_filesize($fieldinfo, $fielddata, $value, $data) {
  return format_size($value);
}

/**
 * Handle a timestamp filter.
 */
function views_handler_filter_timestamp($op, $filter, $filterinfo, &$query) {
  $value = 0;
  if ($filter['value']) {
    $value = $filter['value'] == 'now' ? "***CURRENT_TIME***" : strtotime($filter['value']);
  }
  $table = $filterinfo['table'];
  $column = $filterinfo['field'];
  $field = "{$table}.{$column}";
  if ($filterinfo['from_unixtime']) {
    $field = "from_UNIXTIME({$field})";
  }
  $query
    ->ensure_table($table);
  $query
    ->add_where("%s %s %s + %d", $field, $filter['operator'], $value, $filter['options']);
}

/**
 * Provide validation for filters with array values. The $name
 * must be provided by whatever added the validator to the
 * form gadget.
 */
function views_filter_validate_array($form, $name) {
  $op = $_POST['op'];
  if ($op != t('Save') && $op != t('Save and edit')) {
    return;

    // only validate on saving!
  }
  if (!isset($form['#value']) || $form['#value'] == array()) {
    form_error($form, t('@filter must have a value!', array(
      '@filter' => $name,
    )));
  }
}

/**
 * Provide a form gadget for dates.
 */
function views_handler_filter_date_value_form() {
  return array(
    '#type' => 'textfield',
    '#attributes' => array(
      'class' => 'jscalendar',
    ),
  );
}

/**
 * Substitute current time; this works with cached queries.
 */
function views_views_query_substitutions($view) {
  global $user;
  return array(
    '***CURRENT_TIME***' => time(),
  );
}

/**
 * Returns a themed view.
 * @param $view_name
 *    The name of the view.
 * @param $limit
 *   Maximum number of nodes displayed on one page. if $limit is set and $use_pager is
 *   not, this will be the maximum number of records returned. This is ignored
 *   if using a view set to return a random result.
 *   If NULL, the setting defined for the $view will be used.
 * @param $use_pager
 *   If set, use a pager. Set this to the pager id you want it to use if you
 *   plan on using multiple pagers on a page. Note that the pager element id
 *   will be decremented in order to have the IDs start at 0.
 *   If NULL, the setting defined for the $view will be used.
 * @param $type
 *    'page' -- Produce output as a page, sent through theme.
 *      The only real difference between this and block is that
 *      a page uses drupal_set_title to change the page title.
 *    'block' -- Produce output as a block, sent through theme.
 *    'embed' -- Use this if you want to embed a view onto another page,
 *      and don't want any block or page specific things to happen to it.
 * @param $view_args
 *   An array containing the arguments for the view
 */
function theme_view($view_name, $limit = NULL, $use_pager = NULL, $type = 'embed', $view_args = array()) {
  if ($view = views_get_view($view_name)) {
    $use_pager = isset($use_pager) ? $use_pager : $view->use_pager;
    $limit_default = $type == 'block' ? $view->nodes_per_block : $view->nodes_per_page;
    $limit = isset($limit) ? $limit : $limit_default;
    return views_build_view($type, $view, $view_args, $use_pager, $limit);
  }
}

/**
 * This function is used as a central place to manage some translatable text strings
 * that are used in different places.
 * @param $text
 *   which string to return.
 */
function views_t_strings($text) {
  switch ($text) {
    case 'filter date':
      return t('The "Value" can either be a date in the format: CCYY-MM-DD HH:MM:SS or the word "now" to use the current time. You may enter a positive or negative number in the "Option" field that will represent the amount of seconds that will be added or substracted to the time; this is most useful when combined with "now". If you have the jscalendar module from jstools installed, you can use a popup date picker here.');
  }
}

/**
 * This function fetches filter values from the $_GET object
 */
function views_get_filter_values($input = NULL) {
  if (!isset($input)) {
    $input = $_GET;
  }
  $values = array();
  foreach ($input as $key => $value) {
    if (strpos($key, 'op') === 0) {

      // starts with op
      $values[substr($key, 2)]['op'] = $value;

      // two letters in op
    }
    elseif (strpos($key, 'filter') === 0) {

      // starts with op
      $values[substr($key, 6)]['filter'] = $value;

      // six letters in filter
    }
  }
  return $values;
}

/**
 * Invalidate the views cache when taxonomy vocabulary changes.
 */
function views_taxonomy($op, $type, $object = NULL) {
  if ($type == 'vocabulary' && $op == 'delete' || $op == 'insert' || $op == 'update') {
    views_invalidate_cache();
  }
}
function views_form_alter($form_id, &$form) {
  if ($form_id == 'profile_field_form') {
    views_invalidate_cache();
  }
}

// An implementation of hook_devel_caches() from devel.module. Must be in views.module so it always is included.
function views_devel_caches() {
  return array(
    'cache_views',
  );
}

Functions

Namesort descending Description
theme_view Returns a themed view.
theme_views_display_filters
theme_views_filters
theme_views_handle_field Themeable function to handle displaying a specific field.
theme_views_more Format the 'more' link for a view. Personally I prefer [More] but I've been convinced to go with simply 'more'.
theme_views_nodate
theme_views_summary Display a summary version of a view.
theme_views_view Display a view.
theme_views_view_list Display the nodes of a view as a list.
theme_views_view_nodes Display the nodes of a view as plain nodes.
theme_views_view_table Display the nodes of a view as a table.
theme_views_view_teasers Display the nodes of a view as teasers.
views_access Determine if the specified user has access to a view.
views_block Implementation of hook_block()
views_build_view This builds the basic view.
views_create_view Create a blank view.
views_css_safe Prepare the specified string for use as a CSS identifier.
views_devel_caches
views_filters
views_filters_process
views_filter_validate_array Provide validation for filters with array values. The $name must be provided by whatever added the validator to the form gadget.
views_form_alter
views_get_all_urls Load all of the URLs we use; this is cached in a special manner in an attempt to make the menu system both flexible and yet not overly intensive.
views_get_current_view
views_get_filter_values This function fetches filter values from the $_GET object
views_get_summary_link Get the summary link for a view.
views_get_textarea
views_get_title Figure out what the title of a view should be.
views_get_url Figure out what the URL of the view we're currently looking at is.
views_get_view This function loads a view by name or vid; if not found in db, it looks for a default view by that name.
views_handler_field_date Format a date.
views_handler_field_dates Provide a list of all standard supproted date output handlers.
views_handler_field_date_custom Format a date using custom representation.
views_handler_field_date_large Format a date using large representation.
views_handler_field_date_small Format a date using small representation.
views_handler_field_filesize Format a field as file size.
views_handler_field_int Format a field as an integer.
views_handler_field_since Format a date as "X time ago".
views_handler_filter_date_value_form Provide a form gadget for dates.
views_handler_filter_like Custom filter for LIKE operations
views_handler_filter_null Custom filter for IS NULL and IS NOT NULL operations Operator must be 'IS' or 'IS NOT'
views_handler_filter_timestamp Handle a timestamp filter.
views_handler_operator_andor A list of and/or/nor.
views_handler_operator_eqneq A list of equal or not equal to.
views_handler_operator_gtlt A list of greater / equal / less than
views_handler_operator_like A list of options to be used in LIKE queries
views_handler_operator_or A list of or/nor.
views_handler_operator_yesno A list of yes/no.
views_handler_sort_date
views_handler_sort_date_minute
views_handler_sort_date_options
views_help Implementation of hook_help()
views_invalidate_cache Invalidate the views cache, forcing a rebuild on the next grab of table data.
views_load_cache Load the cache sub-module
views_load_query Load the query sub-module
views_load_view Load a view from the database -- public version of the function.
views_menu Implementation of hook_menu()
views_menu_admin_items Add the adminstrative items to a view.
views_menu_inline_items
views_menu_standard_items Add the menu items for all non-inline views to the menu
views_new_table Helper function to make table creation a little easier. It adds the necessary data to a $table array and returns it.
views_perm Implementation of hook_perm
views_set_breadcrumb
views_set_current_view
views_table_add_data Helper function to make table creation a little easier. It adds the necessary data to the $table array.
views_table_add_field Helper function to make table creation a little easier. It adds the necessary data to the $table array.
views_table_add_filter Helper function to make table creation a little easier. It adds the necessary data to the $table array.
views_table_add_sort Helper function to make table creation a little easier. It adds the necessary data to the $table array.
views_taxonomy Invalidate the views cache when taxonomy vocabulary changes.
views_theme Easily theme any item to a view.
views_theme_field Easily theme any item to a field name. field name will be in the format of TABLENAME_FIELDNAME You have to understand a bit about the views data to utilize this.
views_t_strings This function is used as a central place to manage some translatable text strings that are used in different places.
views_url_arg Handle '$arg' in a URL. Any non-empty value is true.
views_url_node Handle '$node' in a URL. Any valid node wil ldo.
views_url_user Handle '$user' in a URL. Any valid user wil ldo.
views_views_query_substitutions Substitute current time; this works with cached queries.
views_views_style_plugins Default Views style plugins. Implementation of hook_views_style_plugins()
views_views_tabs Implementation of hook_views_tabs().
views_views_url_tokens Default Views URL tokens
views_view_add_argument Add argument info to a view.
views_view_add_block Add block info to a view.
views_view_add_exposed_filter Add exposed_filter info to a view.
views_view_add_field Add field info to a view.
views_view_add_filter Add filter info to a view.
views_view_add_menu Add menu info to a view.
views_view_add_page Add page info to a view.
views_view_add_sort Add sort info to a view.
views_view_block This views a view by block. Can be used as a callback or programmatically.
views_view_page This views a view by page, and should only be used as a callback.
_views_break_phrase
_views_build_filters_form
_views_check_arrays Ensure that all the arrays in a view exist so we don't run into array operations on a non-array error.
_views_construct_header Construct a header for a table view.
_views_create_menu_item Helper function to add a menu item for a view.
_views_delete_view Delete a view from the database.
_views_get_timezone Figure out what timezone we're in; needed for some date manipulations.
_views_is_cacheable Determine whether or not a view is cacheable. A view is not cacheable if there is some kind of user input or data required. For example, views that need to restrict to the 'current' user, or any views that require arguments or allow…
_views_load_view Load a view from the database. (deprecated; use views_load_view in favor of this function).
_views_menu_item Helper function to create a menu item for a view.
_views_menu_type Determine what menu type a view needs to use.
_views_save_view Save a view to the database.
_views_view_fields Provide all the fields in a view.