<?php
// $Id: views.module,v 1.166.2.43 2007/07/14 18:54:16 merlinofchaos Exp $

// ---------------------------------------------------------------------------
// 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;
  }

  $query = db_rewrite_sql($info['query'], 'node');

  $items = array();
  if ($query) {
    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) {
  $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) {
  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%m')";
      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]), '%Y%m%%d')";
      break;
    case 'month':
      $field = "DATE_FORMAT(FROM_UNIXTIME($sortinfo[table].$sortinfo[field]), '%Y%m%)";
      break;
    case 'year':
      $field = "DATE_FORMAT(FROM_UNIXTIME($sortinfo[table].$sortinfo[field]), '%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');
}