<?php
/**
 * @file
 * Primarily Drupal hooks and global API functions to manipulate views.
 *
 * This is the main module file for Views. The main entry points into
 * this module are views_page() and views_block(), where it handles
 * incoming page and block requests.
 */

/**
 * Advertise the current views api version
 */
function views_api_version() {
  return '3.0';
}

/**
 * Implements hook_forms().
 *
 * To provide distinct form IDs for Views forms, the View name and
 * specific display name are appended to the base ID,
 * views_form_views_form. When such a form is built or submitted, this
 * function will return the proper callback function to use for the given form.
 */
function views_forms($form_id, $args) {
  if (strpos($form_id, 'views_form_') === 0) {
    return array(
      $form_id => array(
        'callback' => 'views_form',
      ),
    );
  }
}

/**
 * Returns a form ID for a Views form using the name and display of the View.
 */
function views_form_id($view) {
  $parts = array(
    'views_form',
    $view->name,
    $view->current_display,
  );

  return implode('_', $parts);
}

/**
 * Views will not load plugins advertising a version older than this.
 */
function views_api_minimum_version() {
  return '2';
}

/**
 * Implement hook_theme(). Register views theming functions.
 */
function views_theme($existing, $type, $theme, $path) {
  $path = drupal_get_path('module', 'views');
  ctools_include('theme', 'views', 'theme');

  // Some quasi clever array merging here.
  $base = array(
    'file' => 'theme.inc',
    'path' => $path . '/theme',
  );

  // Our extra version of pager from pager.inc
  $hooks['views_mini_pager'] = $base + array(
    'variables' => array('tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()),
    'pattern' => 'views_mini_pager__',
  );

  $variables = array(
    // For displays, we pass in a dummy array as the first parameter, since
    // $view is an object but the core contextual_preprocess() function only
    // attaches contextual links when the primary theme argument is an array.
    'display' => array('view_array' => array(), 'view' => NULL),
    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
    'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
    'exposed_form' => array('view' => NULL, 'options' => NULL),
    'pager' => array(
      'view' => NULL, 'options' => NULL,
      'tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()
    ),
  );

  // Default view themes
  $hooks['views_view_field'] = $base + array(
    'pattern' => 'views_view_field__',
    'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
  );
  $hooks['views_view_grouping'] = $base + array(
    'pattern' => 'views_view_grouping__',
    'variables' => array('view' => NULL, 'grouping' => NULL, 'grouping_level' => NULL, 'rows' => NULL, 'title' => NULL),
  );

  $plugins = views_fetch_plugin_data();

  // Register theme functions for all style plugins
  foreach ($plugins as $type => $info) {
    foreach ($info as $plugin => $def) {
      if (isset($def['theme']) && (!isset($def['register theme']) || !empty($def['register theme']))) {
        $hooks[$def['theme']] = array(
          'pattern' => $def['theme'] . '__',
          'file' => $def['theme file'],
          'path' => $def['theme path'],
          'variables' => $variables[$type],
        );

        $include = DRUPAL_ROOT . '/' . $def['theme path'] . '/' . $def['theme file'];
        if (file_exists($include)) {
          require_once $include;
        }

        if (!function_exists('theme_' . $def['theme'])) {
          $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
        }
      }
      if (isset($def['additional themes'])) {
        foreach ($def['additional themes'] as $theme => $theme_type) {
          if (empty($theme_type)) {
            $theme = $theme_type;
            $theme_type = $type;
          }

          $hooks[$theme] = array(
            'pattern' => $theme . '__',
            'file' => $def['theme file'],
            'path' => $def['theme path'],
            'variables' => $variables[$theme_type],
          );

          if (!function_exists('theme_' . $theme)) {
            $hooks[$theme]['template'] = drupal_clean_css_identifier($theme);
          }
        }
      }
    }
  }

  $hooks['views_form_views_form'] = $base + array(
    'render element' => 'form',
  );

  $hooks['views_exposed_form'] = $base + array(
    'template' => 'views-exposed-form',
    'pattern' => 'views_exposed_form__',
    'render element' => 'form',
  );

  $hooks['views_more'] = $base + array(
    'template' => 'views-more',
    'pattern' => 'views_more__',
    'variables' => array('more_url' => NULL, 'link_text' => 'more', 'view' => NULL),
  );

  // Add theme suggestions which are part of modules.
  foreach (views_get_module_apis() as $info) {
    if (isset($info['template path'])) {
      $hooks += _views_find_module_templates($hooks, $info['template path']);
    }
  }
  return $hooks;
}

/**
 * Scans a directory of a module for template files.
 *
 * @param $cache
 *   The existing cache of theme hooks to test against.
 * @param $path
 *   The path to search.
 *
 * @see drupal_find_theme_templates
 */
function _views_find_module_templates($cache, $path) {
  $templates = array();
  $regex = '/' . '\.tpl\.php' . '$' . '/';

  // Because drupal_system_listing works the way it does, we check for real
  // templates separately from checking for patterns.
  $files = drupal_system_listing($regex, $path, 'name', 0);
  foreach ($files as $template => $file) {
    // Chop off the remaining extensions if there are any. $template already
    // has the rightmost extension removed, but there might still be more,
    // such as with .tpl.php, which still has .tpl in $template at this point.
    if (($pos = strpos($template, '.')) !== FALSE) {
      $template = substr($template, 0, $pos);
    }
    // Transform - in filenames to _ to match function naming scheme
    // for the purposes of searching.
    $hook = strtr($template, '-', '_');
    if (isset($cache[$hook])) {
      $templates[$hook] = array(
        'template' => $template,
        'path' => dirname($file->filename),
        'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL,
      );
    }
    // Ensure that the pattern is maintained from base themes to its sub-themes.
    // Each sub-theme will have their templates scanned so the pattern must be
    // held for subsequent runs.
    if (isset($cache[$hook]['pattern'])) {
      $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
    }
  }

  $patterns = array_keys($files);

  foreach ($cache as $hook => $info) {
    if (!empty($info['pattern'])) {
      // Transform _ in pattern to - to match file naming scheme
      // for the purposes of searching.
      $pattern = strtr($info['pattern'], '_', '-');

      $matches = preg_grep('/^'. $pattern .'/', $patterns);
      if ($matches) {
        foreach ($matches as $match) {
          $file = substr($match, 0, strpos($match, '.'));
          // Put the underscores back in for the hook name and register this pattern.
          $templates[strtr($file, '-', '_')] = array(
            'template' => $file,
            'path' => dirname($files[$match]->uri),
            'variables' => isset($info['variables']) ? $info['variables'] : NULL,
            'render element' => isset($info['render element']) ? $info['render element'] : NULL,
            'base hook' => $hook,
            'includes' => isset($info['includes']) ? $info['includes'] : NULL,
          );
        }
      }
    }
  }

  return $templates;
}

/**
 * A theme preprocess function to automatically allow view-based node
 * templates if called from a view.
 *
 * The 'modules/node.views.inc' file is a better place for this, but
 * we haven't got a chance to load that file before Drupal builds the
 * node portion of the theme registry.
 */
function views_preprocess_node(&$vars) {
  // The 'view' attribute of the node is added in views_preprocess_node()
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
    $vars['view'] = $vars['node']->view;
    $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name;
    if (!empty($vars['node']->view->current_display)) {
      $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;

      // If a node is being rendered in a view, and the view does not have a path,
      // prevent drupal from accidentally setting the $page variable:
      if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
        $vars['page'] = FALSE;
      }
    }
  }

  // Allow to alter comments and links based on the settings in the row plugin.
  if (!empty($vars['view']->style_plugin->row_plugin) && get_class($vars['view']->style_plugin->row_plugin) == 'views_plugin_row_node_view') {
    node_row_node_view_preprocess_node($vars);
  }
}

/**
 * A theme preprocess function to automatically allow view-based node
 * templates if called from a view.
 */
function views_preprocess_comment(&$vars) {
  // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
    $vars['view'] = &$vars['node']->view;
    $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name;
    if (!empty($vars['node']->view->current_display)) {
      $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
    }
  }
}

/*
 * Implement hook_permission().
 */
function views_permission() {
  return array(
    'administer views' => array(
      'title' => t('Administer views'),
      'description' => t('Access the views administration pages.'),
    ),
    'access all views' => array(
      'title' => t('Bypass views access control'),
      'description' => t('Bypass access control when accessing views.'),
    ),
  );
}

/**
 * Implement hook_menu().
 */
function views_menu() {
  // Any event which causes a menu_rebuild could potentially mean that the
  // Views data is updated -- module changes, profile changes, etc.
  views_invalidate_cache();
  $items = array();
  $items['views/ajax'] = array(
    'title' => 'Views',
    'page callback' => 'views_ajax',
    'theme callback' => 'ajax_base_page_theme',
    'delivery callback' => 'ajax_deliver',
    'access callback' => TRUE,
    'description' => 'Ajax callback for view loading.',
    'type' => MENU_CALLBACK,
    'file' => 'includes/ajax.inc',
  );
  // Path is not admin/structure/views due to menu complications with the wildcards from
  // the generic ajax callback.
  $items['admin/views/ajax/autocomplete/user'] = array(
    'page callback' => 'views_ajax_autocomplete_user',
    'theme callback' => 'ajax_base_page_theme',
    'access callback' => 'user_access',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'file' => 'includes/ajax.inc',
  );
  // Define another taxonomy autocomplete because the default one of drupal
  // does not support a vid a argument anymore
  $items['admin/views/ajax/autocomplete/taxonomy'] = array(
    'page callback' => 'views_ajax_autocomplete_taxonomy',
    'theme callback' => 'ajax_base_page_theme',
    'access callback' => 'user_access',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'file' => 'includes/ajax.inc',
  );
  return $items;
}

/**
 * Implement hook_menu_alter().
 */
function views_menu_alter(&$callbacks) {
  $our_paths = array();
  $views = views_get_applicable_views('uses hook menu');
  foreach ($views as $data) {
    list($view, $display_id) = $data;
    $result = $view->execute_hook_menu($display_id, $callbacks);
    if (is_array($result)) {
      // The menu system doesn't support having two otherwise
      // identical paths with different placeholders.  So we
      // want to remove the existing items from the menu whose
      // paths would conflict with ours.

      // First, we must find any existing menu items that may
      // conflict.  We use a regular expression because we don't
      // know what placeholders they might use.  Note that we
      // first construct the regex itself by replacing %views_arg
      // in the display path, then we use this constructed regex
      // (which will be something like '#^(foo/%[^/]*/bar)$#') to
      // search through the existing paths.
      $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
      $matches = preg_grep($regex, array_keys($callbacks));

      // Remove any conflicting items that were found.
      foreach ($matches as $path) {
        // Don't remove the paths we just added!
        if (!isset($our_paths[$path])) {
          unset($callbacks[$path]);
        }
      }
      foreach ($result as $path => $item) {
        if (!isset($callbacks[$path])) {
          // Add a new item, possibly replacing (and thus effectively
          // overriding) one that we removed above.
          $callbacks[$path] = $item;
        }
        else {
          // This item already exists, so it must be one that we added.
          // We change the various callback arguments to pass an array
          // of possible display IDs instead of a single ID.
          $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
          $callbacks[$path]['page arguments'][1][] = $display_id;
          $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
          $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
          $callbacks[$path]['load arguments'][1][] = $display_id;
        }
        $our_paths[$path] = TRUE;
      }
    }
  }

  // Save memory: Destroy those views.
  foreach ($views as $data) {
    list($view, $display_id) = $data;
    $view->destroy();
  }
}

/**
 * Helper function for menu loading. This will automatically be
 * called in order to 'load' a views argument; primarily it
 * will be used to perform validation.
 *
 * @param $value
 *   The actual value passed.
 * @param $name
 *   The name of the view. This needs to be specified in the 'load function'
 *   of the menu entry.
 * @param $display_id
 *   The display id that will be loaded for this menu item.
 * @param $index
 *   The menu argument index. This counts from 1.
 */
function views_arg_load($value, $name, $display_id, $index) {
  static $views = array();

  // Make sure we haven't already loaded this views argument for a similar menu
  // item elsewhere.
  $key = $name . ':' . $display_id . ':' . $value . ':' . $index;
  if (isset($views[$key])) {
    return $views[$key];
  }

  if ($view = views_get_view($name)) {
    $view->set_display($display_id);
    $view->init_handlers();

    $ids = array_keys($view->argument);

    $indexes = array();
    $path = explode('/', $view->get_path());

    foreach ($path as $id => $piece) {
      if ($piece == '%' && !empty($ids)) {
        $indexes[$id] = array_shift($ids);
      }
    }

    if (isset($indexes[$index])) {
      if (isset($view->argument[$indexes[$index]])) {
        $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
        $view->destroy();

        // Store the output in case we load this same menu item again.
        $views[$key] = $arg;
        return $arg;
      }
    }
    $view->destroy();
  }
}

/**
 * Page callback entry point; requires a view and a display id, then
 * passes control to the display handler.
 */
function views_page() {
  $args = func_get_args();
  $name = array_shift($args);
  $display_id = array_shift($args);

  // Load the view and render it.
  if ($view = views_get_view($name)) {
    return $view->execute_display($display_id, $args);
  }

  // Fallback; if we get here no view was found or handler was not valid.
  return drupal_not_found();
}

/**
 * Implements hook_page_alter().
 */
function views_page_alter(&$page) {
  // If the main content of this page contains a view, attach its contextual
  // links to the overall page array. This allows them to be rendered directly
  // next to the page title.
  $view = views_get_page_view();
  if (!empty($view)) {
    // If a module is still putting in the display like we used to, catch that.
    if (is_subclass_of($view, 'views_plugin_display')) {
      $view = $view->view;
    }

    views_add_contextual_links($page, 'page', $view, $view->current_display);
  }
}

/**
 * Implements MODULE_preprocess_HOOK().
 */
function views_preprocess_html(&$variables) {
  // If the page contains a view as its main content, contextual links may have
  // been attached to the page as a whole; for example, by views_page_alter().
  // This allows them to be associated with the page and rendered by default
  // next to the page title (which we want). However, it also causes the
  // Contextual Links module to treat the wrapper for the entire page (i.e.,
  // the <body> tag) as the HTML element that these contextual links are
  // associated with. This we don't want; for better visual highlighting, we
  // prefer a smaller region to be chosen. The region we prefer differs from
  // theme to theme and depends on the details of the theme's markup in
  // page.tpl.php, so we can only find it using JavaScript. We therefore remove
  // the "contextual-links-region" class from the <body> tag here and add
  // JavaScript that will insert it back in the correct place.
  if (!empty($variables['page']['#views_contextual_links_info'])) {
    $key = array_search('contextual-links-region', $variables['classes_array']);
    if ($key !== FALSE) {
      unset($variables['classes_array'][$key]);
      // Add the JavaScript, with a group and weight such that it will run
      // before modules/contextual/contextual.js.
      drupal_add_js(drupal_get_path('module', 'views') . '/js/views-contextual.js', array('group' => JS_LIBRARY, 'weight' => -1));
    }
  }
}

/**
 * Implements hook_contextual_links_view_alter().
 */
function views_contextual_links_view_alter(&$element, $items) {
  // If we are rendering views-related contextual links attached to the overall
  // page array, add a class to the list of contextual links. This will be used
  // by the JavaScript added in views_preprocess_html().
  if (!empty($element['#element']['#views_contextual_links_info']) && !empty($element['#element']['#type']) && $element['#element']['#type'] == 'page') {
    $element['#attributes']['class'][] = 'views-contextual-links-page';
  }
}

/**
 * Implement hook_block_info().
 */
function views_block_info() {
  // Try to avoid instantiating all the views just to get the blocks info.
  views_include('cache');
  $cache = views_cache_get('views_block_items', TRUE);
  if ($cache && is_array($cache->data)) {
    return $cache->data;
  }

  $items = array();
  $views = views_get_all_views();
  foreach ($views as $view) {
    // disabled views get nothing.
    if (!empty($view->disabled)) {
      continue;
    }

    $view->init_display();
    foreach ($view->display as $display_id => $display) {

      if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
        $result = $display->handler->execute_hook_block_list();
        if (is_array($result)) {
          $items = array_merge($items, $result);
        }
      }

      if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
        $result = $display->handler->get_special_blocks();
        if (is_array($result)) {
          $items = array_merge($items, $result);
        }
      }
    }
  }

  // block.module has a delta length limit of 32, but our deltas can
  // unfortunately be longer because view names can be 32 and display IDs
  // can also be 32. So for very long deltas, change to md5 hashes.
  $hashes = array();

  // get the keys because we're modifying the array and we don't want to
  // confuse PHP too much.
  $keys = array_keys($items);
  foreach ($keys as $delta) {
    if (strlen($delta) >= 32) {
      $hash = md5($delta);
      $hashes[$hash] = $delta;
      $items[$hash] = $items[$delta];
      unset($items[$delta]);
    }
  }

  // Only save hashes if they have changed.
  $old_hashes = variable_get('views_block_hashes', array());
  if ($hashes != $old_hashes) {
    variable_set('views_block_hashes', $hashes);
  }
  // Save memory: Destroy those views.
  foreach ($views as $view) {
    $view->destroy();
  }

  views_cache_set('views_block_items', $items, TRUE);

  return $items;
}

/**
 * Implement hook_block_view().
 */
function views_block_view($delta) {
  $start = microtime(TRUE);
  // if this is 32, this should be an md5 hash.
  if (strlen($delta) == 32) {
    $hashes = variable_get('views_block_hashes', array());
    if (!empty($hashes[$delta])) {
      $delta = $hashes[$delta];
    }
  }

  // This indicates it's a special one.
  if (substr($delta, 0, 1) == '-') {
    list($nothing, $type, $name, $display_id) = explode('-', $delta);
    // Put the - back on.
    $type = '-' . $type;
    if ($view = views_get_view($name)) {
      if ($view->access($display_id)) {
        $view->set_display($display_id);
        if (isset($view->display_handler)) {
          $output = $view->display_handler->view_special_blocks($type);
          // Before returning the block output, convert it to a renderable
          // array with contextual links.
          views_add_block_contextual_links($output, $view, $display_id, 'special_block_' . $type);
          $view->destroy();
          return $output;
        }
      }
      $view->destroy();
    }
  }

  // If the delta doesn't contain valid data return nothing.
  $explode = explode('-', $delta);
  if (count($explode) != 2) {
    return;
  }
  list($name, $display_id) = $explode;
  // Load the view
  if ($view = views_get_view($name)) {
    if ($view->access($display_id)) {
      $output = $view->execute_display($display_id);
      // Before returning the block output, convert it to a renderable array
      // with contextual links.
      views_add_block_contextual_links($output, $view, $display_id);
      $view->destroy();
      return $output;
    }
    $view->destroy();
  }
}

/**
 * Converts Views block content to a renderable array with contextual links.
 *
 * @param $block
 *   An array representing the block, with the same structure as the return
 *   value of hook_block_view(). This will be modified so as to force
 *   $block['content'] to be a renderable array, containing the optional
 *   '#contextual_links' property (if there are any contextual links associated
 *   with the block).
 * @param $view
 *   The view that was used to generate the block content.
 * @param $display_id
 *   The ID of the display within the view that was used to generate the block
 *   content.
 * @param $block_type
 *   The type of the block. If it's block it's a regular views display,
 *   but 'special_block_-exp' exist as well.
 */
function views_add_block_contextual_links(&$block, $view, $display_id, $block_type = 'block') {
  // Do not add contextual links to an empty block.
  if (!empty($block['content'])) {
    // Contextual links only work on blocks whose content is a renderable
    // array, so if the block contains a string of already-rendered markup,
    // convert it to an array.
    if (is_string($block['content'])) {
      $block['content'] = array('#markup' => $block['content']);
    }
    // Add the contextual links.
    views_add_contextual_links($block['content'], $block_type, $view, $display_id);
  }
}

/**
 * Adds contextual links associated with a view display to a renderable array.
 *
 * This function should be called when a view is being rendered in a particular
 * location and you want to attach the appropriate contextual links (e.g.,
 * links for editing the view) to it.
 *
 * The function operates by checking the view's display plugin to see if it has
 * defined any contextual links that are intended to be displayed in the
 * requested location; if so, it attaches them. The contextual links intended
 * for a particular location are defined by the 'contextual links' and
 * 'contextual links locations' properties in hook_views_plugins() and
 * hook_views_plugins_alter(); as a result, these hook implementations have
 * full control over where and how contextual links are rendered for each
 * display.
 *
 * In addition to attaching the contextual links to the passed-in array (via
 * the standard #contextual_links property), this function also attaches
 * additional information via the #views_contextual_links_info property. This
 * stores an array whose keys are the names of each module that provided
 * views-related contextual links (same as the keys of the #contextual_links
 * array itself) and whose values are themselves arrays whose keys ('location',
 * 'view_name', and 'view_display_id') store the location, name of the view,
 * and display ID that were passed in to this function. This allows you to
 * access information about the contextual links and how they were generated in
 * a variety of contexts where you might be manipulating the renderable array
 * later on (for example, alter hooks which run later during the same page
 * request).
 *
 * @param $render_element
 *   The renderable array to which contextual links will be added. This array
 *   should be suitable for passing in to drupal_render() and will normally
 *   contain a representation of the view display whose contextual links are
 *   being requested.
 * @param $location
 *   The location in which the calling function intends to render the view and
 *   its contextual links. The core system supports three options for this
 *   parameter:
 *   - 'block': Used when rendering a block which contains a view. This
 *     retrieves any contextual links intended to be attached to the block
 *     itself.
 *   - 'page': Used when rendering the main content of a page which contains a
 *     view. This retrieves any contextual links intended to be attached to the
 *     page itself (for example, links which are displayed directly next to the
 *     page title).
 *   - 'view': Used when rendering the view itself, in any context. This
 *     retrieves any contextual links intended to be attached directly to the
 *     view.
 *   If you are rendering a view and its contextual links in another location,
 *   you can pass in a different value for this parameter. However, you will
 *   also need to use hook_views_plugins() or hook_views_plugins_alter() to
 *   declare, via the 'contextual links locations' array key, which view
 *   displays support having their contextual links rendered in the location
 *   you have defined.
 * @param $view
 *   The view whose contextual links will be added.
 * @param $display_id
 *   The ID of the display within $view whose contextual links will be added.
 *
 * @see hook_views_plugins()
 * @see views_block_view()
 * @see views_page_alter()
 * @see template_preprocess_views_view()
 */
function views_add_contextual_links(&$render_element, $location, $view, $display_id) {
  // Do not do anything if the view is configured to hide its administrative
  // links.
  if (empty($view->hide_admin_links)) {
    // Also do not do anything if the display plugin has not defined any
    // contextual links that are intended to be displayed in the requested
    // location.
    $plugin = views_fetch_plugin_data('display', $view->display[$display_id]->display_plugin);
    // If contextual links locations are not set, provide a sane default. (To
    // avoid displaying any contextual links at all, a display plugin can still
    // set 'contextual links locations' to, e.g., an empty array.)
    $plugin += array('contextual links locations' => array('view'));
    // On exposed_forms blocks contextual links should always be visible.
    $plugin['contextual links locations'][] = 'special_block_-exp';
    $has_links = !empty($plugin['contextual links']) && !empty($plugin['contextual links locations']);
    if ($has_links && in_array($location, $plugin['contextual links locations'])) {
      foreach ($plugin['contextual links'] as $module => $link) {
        $args = array();
        $valid = TRUE;
        if (!empty($link['argument properties'])) {
          foreach ($link['argument properties'] as $property) {
            // If the plugin is trying to create an invalid contextual link
            // (for example, "path/to/{$view->property}", where $view->property
            // does not exist), we cannot construct the link, so we skip it.
            if (!property_exists($view, $property)) {
              $valid = FALSE;
              break;
            }
            else {
              $args[] = $view->{$property};
            }
          }
        }
        // If the link was valid, attach information about it to the renderable
        // array.
        if ($valid) {
          $render_element['#contextual_links'][$module] = array($link['parent path'], $args);
          $render_element['#views_contextual_links_info'][$module] = array(
            'location' => $location,
            'view' => $view,
            'view_name' => $view->name,
            'view_display_id' => $display_id,
          );
        }
      }
    }
  }
}

/**
 * Returns an array of language names.
 *
 * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
 *
 * @param $field
 *   'name' => names in current language, localized
 *   'native' => native names
 * @param $all
 *   Boolean to return all languages or only enabled ones
 *
 * @see locale_language_list
 */
function views_language_list($field = 'name', $all = FALSE) {
  if ($all) {
    $languages = language_list();
  }
  else {
    $languages = language_list('enabled');
    $languages = $languages[1];
  }
  $list = array();
  foreach ($languages as $language) {
    $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
  }
  return $list;
}

/**
 * Implements hook_flush_caches().
 */
function views_flush_caches() {
  return array('cache_views', 'cache_views_data');
}

/**
 * Implements hook_field_create_instance.
 */
function views_field_create_instance($instance) {
  cache_clear_all('*', 'cache_views', TRUE);
  cache_clear_all('*', 'cache_views_data', TRUE);
}

/**
 * Implements hook_field_update_instance.
 */
function views_field_update_instance($instance, $prior_instance) {
  cache_clear_all('*', 'cache_views', TRUE);
  cache_clear_all('*', 'cache_views_data', TRUE);
}

/**
 * Implements hook_field_delete_instance.
 */
function views_field_delete_instance($instance) {
  cache_clear_all('*', 'cache_views', TRUE);
  cache_clear_all('*', 'cache_views_data', 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);
}

/**
 * Access callback to determine if the user can import Views.
 *
 * View imports require an additional access check because they are PHP
 * code and PHP is more locked down than administer views.
 */
function views_import_access() {
  return user_access('administer views') && user_access('use PHP for settings');
}

/**
 * Determine if the logged in user has access to a view.
 *
 * This function should only be called from a menu hook or some other
 * embedded source. Each argument is the result of a call to
 * views_plugin_access::get_access_callback() which is then used
 * to determine if that display is accessible. If *any* argument
 * is accessible, then the view is accessible.
 */
function views_access() {
  $args = func_get_args();
  foreach ($args as $arg) {
    if ($arg === TRUE) {
      return TRUE;
    }

    if (!is_array($arg)) {
      continue;
    }

    list($callback, $arguments) = $arg;
    $arguments = $arguments ? $arguments : array();
    // Bring dynamic arguments to the access callback.
    foreach ($arguments as $key => $value) {
      if (is_int($value) && isset($args[$value])) {
        $arguments[$key] = $args[$value];
      }
    }
    if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
      return TRUE;
    }
  }

  return FALSE;
}

/**
 * Access callback for the views_plugin_access_perm access plugin.
 *
 * Determine if the specified user has access to a view on the basis of
 * permissions. If the $account argument is omitted, the current user
 * is used.
 */
function views_check_perm($perm, $account = NULL) {
  return user_access($perm, $account) || user_access('access all views', $account);
}

/**
 * Access callback for the views_plugin_access_role access plugin.

 * Determine if the specified user has access to a view on the basis of any of
 * the requested roles. If the $account argument is omitted, the current user
 * is used.
 */
function views_check_roles($rids, $account = NULL) {
  global $user;
  $account = isset($account) ? $account : $user;
  $roles = array_keys($account->roles);
  $roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
  return user_access('access all views', $account) || array_intersect(array_filter($rids), $roles);
}
// ------------------------------------------------------------------
// Functions to help identify views that are running or ran

/**
 * Set the current 'page view' that is being displayed so that it is easy
 * for other modules or the theme to identify.
 */
function &views_set_page_view($view = NULL) {
  static $cache = NULL;
  if (isset($view)) {
    $cache = $view;
  }

  return $cache;
}

/**
 * Find out what, if any, page view is currently in use. Please note that
 * this returns a reference, so be careful! You can unintentionally modify the
 * $view object.
 *
 * @return view
 *   A fully formed, empty $view object.
 */
function &views_get_page_view() {
  return views_set_page_view();
}

/**
 * Set the current 'current view' that is being built/rendered so that it is
 * easy for other modules or items in drupal_eval to identify
 *
 * @return view
 */
function &views_set_current_view($view = NULL) {
  static $cache = NULL;
  if (isset($view)) {
    $cache = $view;
  }

  return $cache;
}

/**
 * Find out what, if any, current view is currently in use. Please note that
 * this returns a reference, so be careful! You can unintentionally modify the
 * $view object.
 *
 * @return view
 */
function &views_get_current_view() {
  return views_set_current_view();
}

// ------------------------------------------------------------------
// Include file helpers

/**
 * Include views .inc files as necessary.
 */
function views_include($file) {
  ctools_include($file, 'views');
}

/**
 * Load views files on behalf of modules.
 */
function views_module_include($api, $reset = FALSE) {
  if ($reset) {
    $cache = &drupal_static('ctools_plugin_api_info');
    if (isset($cache['views']['views'])) {
      unset($cache['views']['views']);
    }
  }
  ctools_include('plugins');
  return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version());
}

/**
 * Get a list of modules that support the current views API.
 */
function views_get_module_apis($api = 'views', $reset = FALSE) {
  if ($reset) {
    $cache = &drupal_static('ctools_plugin_api_info');
    if (isset($cache['views']['views'])) {
      unset($cache['views']['views']);
    }
  }
  ctools_include('plugins');
  return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version());
}

/**
 * Include views .css files.
 */
function views_add_css($file) {
  // We set preprocess to FALSE because we are adding the files conditionally,
  // and we don't want to generate duplicate cache files.
  // TODO: at some point investigate adding some files unconditionally and
  // allowing preprocess.
  drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE));
}

/**
 * Include views .js files.
 */
function views_add_js($file) {
  // If javascript has been disabled by the user, never add js files.
  if (variable_get('views_no_javascript', FALSE)) {
    return;
  }
  static $base = TRUE, $ajax = TRUE;
  if ($base) {
    drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
    $base = FALSE;
  }
  if ($ajax && in_array($file, array('ajax', 'ajax_view'))) {
    drupal_add_library('system', 'drupal.ajax');
    drupal_add_library('system', 'jquery.form');
    $ajax = FALSE;
  }
  ctools_add_js($file, 'views');
}

/**
 * Load views files on behalf of modules.
 */
function views_include_handlers($reset = FALSE) {
  static $finished = FALSE;
  // Ensure this only gets run once.
  if ($finished && !$reset) {
    return;
  }

  views_include('base');
  views_include('handlers');
  views_include('cache');
  views_include('plugins');
  views_module_include('views', $reset);
  $finished = TRUE;
}

// -----------------------------------------------------------------------
// Views handler functions

/**
 * Fetch a handler from the data cache.
 *
 * @param $table
 *   The name of the table this handler is from.
 * @param $field
 *   The name of the field this handler is from.
 * @param $key
 *   The type of handler. i.e, sort, field, argument, filter, relationship
 * @param $override
 *   Override the actual handler object with this class. Used for
 *   aggregation when the handler is redirected to the aggregation
 *   handler.
 *
 * @return views_handler
 *   An instance of a handler object. May be views_handler_broken.
 */
function views_get_handler($table, $field, $key, $override = NULL) {
  static $recursion_protection = array();

  $data = views_fetch_data($table, FALSE);
  $handler = NULL;
  views_include('handlers');

  // Support old views_data entries conversion.

  // Support conversion on table level.
  if (isset($data['moved to'])) {
    $moved = array($data['moved to'], $field);
  }
  // Support conversion on datafield level.
  if (isset($data[$field]['moved to'])) {
    $moved = $data[$field]['moved to'];
  }
  // Support conversion on handler level.
  if (isset($data[$field][$key]['moved to'])) {
    $moved = $data[$field][$key]['moved to'];
  }

  if (isset($data[$field][$key]) || !empty($moved)) {
    if (!empty($moved)) {
      list($moved_table, $moved_field) = $moved;
      if (!empty($recursion_protection[$moved_table][$moved_field])) {
        // recursion detected!
        return NULL;
      }

      $recursion_protection[$moved_table][$moved_field] = TRUE;
      $handler = views_get_handler($moved_table, $moved_field, $key, $override);
      $recursion_protection = array();
      if ($handler) {
        // store these values so we know what we were originally called.
        $handler->original_table = $table;
        $handler->original_field = $field;
        if (empty($handler->actual_table)) {
          $handler->actual_table = $moved_table;
          $handler->actual_field = $moved_field;
        }
      }
      return $handler;
    }

    // Set up a default handler:
    if (empty($data[$field][$key]['handler'])) {
      $data[$field][$key]['handler'] = 'views_handler_' . $key;
    }

    if ($override) {
      $data[$field][$key]['override handler'] = $override;
    }

    $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
  }

  if ($handler) {
    return $handler;
  }

  // DEBUG -- identify missing handlers
  vpr("Missing handler: @table @field @key", array('@table' => $table, '@field' => $field, '@key' => $key));
  $broken = array(
    'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
    'handler' => 'views_handler_' . $key . '_broken',
    'table' => $table,
    'field' => $field,
  );
  return _views_create_handler($broken, 'handler', $key);
}

/**
 * Fetch Views' data from the cache
 */
function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
  views_include('cache');
  return _views_fetch_data($table, $move, $reset);
}

// -----------------------------------------------------------------------
// Views plugin functions

/**
 * Fetch the plugin data from cache.
 */
function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
  views_include('cache');
  return _views_fetch_plugin_data($type, $plugin, $reset);
}

/**
 * Fetch a list of all base tables available
 *
 * @param $type
 *   Either 'display', 'style' or 'row'
 * @param $key
 *   For style plugins, this is an optional type to restrict to. May be 'normal',
 *   'summary', 'feed' or others based on the neds of the display.
 * @param $base
 *   An array of possible base tables.
 *
 * @return
 *   A keyed array of in the form of 'base_table' => 'Description'.
 */
function views_fetch_plugin_names($type, $key = NULL, $base = array()) {
  $data = views_fetch_plugin_data();

  $plugins[$type] = array();

  foreach ($data[$type] as $id => $plugin) {
    // Skip plugins that don't conform to our key.
    if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
      continue;
    }
    if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) {
      $plugins[$type][$id] = $plugin['title'];
    }
  }

  if (!empty($plugins[$type])) {
    asort($plugins[$type]);
    return $plugins[$type];
  }
  // fall-through
  return array();
}

/**
 * Get a handler for a plugin
 *
 * @return views_plugin
 *
 * The created plugin object.
 */
function views_get_plugin($type, $plugin, $reset = FALSE) {
  views_include('handlers');
  $definition = views_fetch_plugin_data($type, $plugin, $reset);
  if (!empty($definition)) {
    return _views_create_handler($definition, $type);
  }
}

/**
 * Load the current enabled localization plugin.
 *
 * @return The name of the localization plugin.
 */
function views_get_localization_plugin() {
  $plugin = variable_get('views_localization_plugin', '');
  // Provide sane default values for the localization plugin.
  if (empty($plugin)) {
    if (module_exists('locale')) {
      $plugin = 'core';
    }
    else {
      $plugin = 'none';
    }
  }

  return $plugin;
}

// -----------------------------------------------------------------------
// Views database functions

/**
 * Get all view templates.
 *
 * Templates are special in-code views that are never active, but exist only
 * to be cloned into real views as though they were templates.
 */
function views_get_all_templates() {
  $templates = array();
  $modules = views_module_include('views_template');

  foreach ($modules as $module => $info) {
    $function = $module . '_views_templates';
    if (function_exists($function)) {
      $new = $function();
      if ($new && is_array($new)) {
        $templates = array_merge($new, $templates);
      }
    }
  }

  return $templates;
}

/**
 * Create an empty view to work with.
 *
 * @return view
 *   A fully formed, empty $view object. This object must be populated before
 *   it can be successfully saved.
 */
function views_new_view() {
  views_include('view');
  $view = new view();
  $view->vid = 'new';
  $view->add_display('default');

  return $view;
}

/**
 * Return a list of all views and display IDs that have a particular
 * setting in their display's plugin settings.
 *
 * @return
 * @code
 * array(
 *   array($view, $display_id),
 *   array($view, $display_id),
 * );
 * @endcode
 */
function views_get_applicable_views($type) {
  // @todo: Use a smarter flagging system so that we don't have to
  // load every view for this.
  $result = array();
  $views = views_get_all_views();

  foreach ($views as $view) {
    // Skip disabled views.
    if (!empty($view->disabled)) {
      continue;
    }

    if (empty($view->display)) {
      // Skip this view as it is broken.
      vsm(t("Skipping broken view @view", array('@view' => $view->name)));
      continue;
    }

    // Loop on array keys because something seems to muck with $view->display
    // a bit in PHP4.
    foreach (array_keys($view->display) as $id) {
      $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
      if (!empty($plugin[$type])) {
        // This view uses hook menu. Clone it so that different handlers
        // don't trip over each other, and add it to the list.
        $v = $view->clone_view();
        if ($v->set_display($id) && $v->display_handler->get_option('enabled')) {
          $result[] = array($v, $id);
        }
        // In PHP 4.4.7 and presumably earlier, if we do not unset $v
        // here, we will find that it actually overwrites references
        // possibly due to shallow copying issues.
        unset($v);
      }
    }
  }
  return $result;
}

/**
 * Return an array of all views as fully loaded $view objects.
 *
 * @param $reset
 *   If TRUE, reset the static cache forcing views to be reloaded.
 */
function views_get_all_views($reset = FALSE) {
  ctools_include('export');
  return ctools_export_crud_load_all('views_view', $reset);
}

/**
 * Returns an array of all enabled views, as fully loaded $view objects.
 */
function views_get_enabled_views() {
  $views = views_get_all_views();
  return array_filter($views, 'views_view_is_enabled');
}

/**
 * Returns an array of all disabled views, as fully loaded $view objects.
 */
function views_get_disabled_views() {
  $views = views_get_all_views();
  return array_filter($views, 'views_view_is_disabled');
}

/**
 * Return an array of view as options array, that can be used by select,
 * checkboxes and radios as #options.
 *
 * @param bool $views_only
 *  If TRUE, only return views, not displays.
 * @param string $filter
 *  Filters the views on status. Can either be 'all' (default), 'enabled' or
 *  'disabled'
 * @param  mixed $exclude_view
 *  view or current display to exclude
 *  either a
 *  - views object (containing $exclude_view->name and $exclude_view->current_display)
 *  - views name as string:  e.g. my_view
 *  - views name and display id (separated by ':'): e.g. my_view:default
 * @param bool $optgroup
 *  If TRUE, returns an array with optgroups for each view (will be ignored for
 *  $views_only = TRUE). Can be used by select
 *
 * @return array
 *  an associative array for use in select.
 *  - key: view name and display id separated by ':', or the view name only
 */
function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE) {

  // Filter the big views array.
  switch ($filter) {
    case 'all':
    case 'disabled':
    case 'enabled':
      $func = "views_get_{$filter}_views";
      $views = $func();
      break;
    default:
      return array();
  }

  // Prepare exclude view strings for comparison.
  if (empty($exclude_view)) {
    $exclude_view_name = '';
    $exclude_view_display = '';
  }
  elseif (is_object($exclude_view)) {
    $exclude_view_name = $exclude_view->name;
    $exclude_view_display = $exclude_view->current_display;
  }
  else {
    list($exclude_view_name, $exclude_view_display) = explode(':', $exclude_view);
  }

  $options = array();
  foreach ($views as $view) {
    // Return only views.
    if ($views_only && $view->name != $exclude_view_name) {
      $options[$view->name] = $view->get_human_name();
    }
    // Return views with display ids.
    else {
      foreach ($view->display as $display_id => $display) {
        if (!($view->name == $exclude_view_name && $display_id == $exclude_view_display)) {
          if ($optgroup) {
            $options[$view->name][$view->name . ':' . $display->id] = t('@view : @display', array('@view' => $view->name, '@display' => $display->id));
          }
          else {
            $options[$view->name . ':' . $display->id] = t('View: @view - Display: @display', array('@view' => $view->name, '@display' => $display->id));
          }
        }
      }
    }
  }
  return $options;
}

/**
 * Returns TRUE if a view is enabled, FALSE otherwise.
 */
function views_view_is_enabled($view) {
  return empty($view->disabled);
}

/**
 * Returns TRUE if a view is disabled, FALSE otherwise.
 */
function views_view_is_disabled($view) {
  return !empty($view->disabled);
}

/**
 * Get a view from the database or from default views.
 *
 * This function is just a static wrapper around views::load(). This function
 * isn't called 'views_load()' primarily because it might get a view
 * from the default views which aren't technically loaded from the database.
 *
 * @param $name
 *   The name of the view.
 * @param $reset
 *   If TRUE, reset this entry in the load cache.
 * @return view
 *   A reference to the $view object. Use $reset if you're sure you want
 *   a fresh one.
 */
function views_get_view($name, $reset = FALSE) {
  if ($reset) {
    $cache = &drupal_static('ctools_export_load_object');
    if (isset($cache['views_view'][$name])) {
      unset($cache['views_view'][$name]);
    }
  }

  ctools_include('export');
  $view = ctools_export_crud_load('views_view', $name);
  if ($view) {
    $view->update();
    return $view->clone_view();
  }
}

/**
 * Find the real location of a table.
 *
 * If a table has moved, find the new name of the table so that we can
 * change its name directly in options where necessary.
 */
function views_move_table($table) {
  $data = views_fetch_data($table, FALSE);
  if (isset($data['moved to'])) {
    $table = $data['moved to'];
  }

  return $table;
}

/**
 * Export callback to load the view subrecords, which are the displays.
 */
function views_load_display_records(&$views) {
  // Get vids from the views.
  $names = array();
  foreach ($views as $view) {
    if (empty($view->display)) {
      $names[$view->vid] = $view->name;
    }
  }

  if (empty($names)) {
    return;
  }

  foreach (view::db_objects() as $key) {
    $object_name = "views_$key";
    $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
      array(':vids' => array_keys($names)));

    foreach ($result as $data) {
      $object = new $object_name(FALSE);
      $object->load_row($data);

      // Because it can get complicated with this much indirection,
      // make a shortcut reference.
      $location = &$views[$names[$object->vid]]->$key;

      // If we have a basic id field, load the item onto the view based on
      // this ID, otherwise push it on.
      if (!empty($object->id)) {
        $location[$object->id] = $object;
      }
      else {
        $location[] = $object;
      }
    }
  }
}

/**
 * Export CRUD callback to save a view.
 */
function views_save_view(&$view) {
  return $view->save();
}

/**
 * Export CRUD callback to delete a view.
 */
function views_delete_view(&$view) {
  return $view->delete(TRUE);
}

/**
 * Export CRUD callback to export a view.
 */
function views_export_view(&$view, $indent = '') {
  return $view->export($indent);
}

// ------------------------------------------------------------------
// Views debug helper functions

/**
 * Provide debug output for Views.
 *
 * This relies on devel.module
 * or on the debug() function if you use a simpletest.
 *
 * @param $message
 *   The message/variable which should be debugged.
 *   This either could be
 *     * an array/object which is converted to pretty output
 *     * a translation source string which is used together with the parameter placeholders.
 *
 * @param $placeholder
 *   The placeholders which are used for the translation source string.
 */
function views_debug($message, $placeholders = array()) {
  if (!is_string($message)) {
    $output = '<pre>' . var_export($message, TRUE) . '</pre>';
  }
  if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
    $devel_region = variable_get('views_devel_region', 'footer');
    if ($devel_region == 'watchdog') {
      $output = $message;
      watchdog('views_logging', $output, $placeholders);
    }
    else if ($devel_region == 'drupal_debug') {
      $output = empty($output) ? t($message, $placeholders) : $output;
      dd($output);
    }
    else {
      $output = empty($output) ? t($message, $placeholders) : $output;
      dpm($output);
    }
  }
  elseif (isset($GLOBALS['drupal_test_info'])) {
    $output = empty($output) ? t($message, $placeholders) : $output;
    debug($output);
  }
}

/**
 * Shortcut to views_debug()
 */
function vpr($message, $placeholders = array()) {
  views_debug($message, $placeholders);
}

/**
 * Debug messages
 */
function vsm($message) {
  if (module_exists('devel')) {
    dpm($message);
  }
}

function views_trace() {
  $message = '';
  foreach (debug_backtrace() as $item) {
    if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
      $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
    }
  }
  return $message;
}

function vsm_trace() {
  vsm(views_trace());
}

function vpr_trace() {
  dpr(views_trace());
}

// ------------------------------------------------------------------
// Views form (View with form elements)

/**
 * Returns TRUE if the passed-in view contains handlers with views form
 * implementations, FALSE otherwise.
 */
function views_view_has_form_elements($view) {
  foreach ($view->field as $field) {
    if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
      return TRUE;
    }
  }
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
  $empty = empty($view->result);
  foreach ($area_handlers as $area) {
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * This is the entry function. Just gets the form for the current step.
 * The form is always assumed to be multistep, even if it has only one
 * step (the default 'views_form_views_form' step). That way it is actually
 * possible for modules to have a multistep form if they need to.
 */
function views_form($form, &$form_state, $view, $output) {
  $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';

  $form = array();
  $query = drupal_get_query_parameters($_GET, array('q'));
  $form['#action'] = url($view->get_url(), array('query' => $query));
  $form['#validate'] = array('views_form_validate');
  $form['#submit'] = array('views_form_submit');
  // Tell the preprocessor whether it should hide the header, footer, pager...
  $form['show_view_elements'] = array(
    '#type' => 'value',
    '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
  );

  $form = $form_state['step']($form, $form_state, $view, $output);
  return $form;
}

/**
 * The basic form validate handler.
 * Fires the hook_views_form_validate() function.
 */
function views_form_validate($form, &$form_state) {
  // Fire the hook. Doesn't use module_invoke_all() because $form_state needs
  // to be passed by reference.
  foreach (module_implements('views_form_validate') as $module) {
    $function = $module . '_views_form_validate';
    $function($form, $form_state);
  }
}

/**
 * The basic form submit handler.
 * Fires the hook_views_form_submit() function.
 */
function views_form_submit($form, &$form_state) {
  // Fire the hook. Doesn't use module_invoke_all() because $form_state needs
  // to be passed by reference.
  foreach (module_implements('views_form_submit') as $module) {
    $function = $module . '_views_form_submit';
    $function($form, $form_state);
  }
}

/**
 * Callback for the main step of a Views form.
 * Invoked by views_form().
 */
function views_form_views_form($form, &$form_state, $view, $output) {
  $form['#prefix'] = '<div class="views-form">';
  $form['#suffix'] = '</div>';
  $form['#theme'] = 'views_form_views_form';
  $form['#validate'][] = 'views_form_views_form_validate';
  $form['#submit'][] = 'views_form_views_form_submit';

  // Add the output markup to the form array so that it's included when the form
  // array is passed to the theme function.
  $form['output'] = array(
    '#type' => 'markup',
    '#markup' => $output,
    // This way any additional form elements will go before the view
    // (below the exposed widgets).
    '#weight' => 50,
  );

  $substitutions = array();
  foreach ($view->field as $field_name => $field) {
    $form_element_name = $field_name;
    if (method_exists($field, 'form_element_name')) {
      $form_element_name = $field->form_element_name();
    }
    $method_form_element_row_id_exists = FALSE;
    if (method_exists($field, 'form_element_row_id')) {
      $method_form_element_row_id_exists = TRUE;
    }

    // If the field provides a views form, allow it to modify the $form array.
    $has_form = FALSE;
    if (property_exists($field, 'views_form_callback')) {
      $callback = $field->views_form_callback;
      $callback($view, $field, $form, $form_state);
      $has_form = TRUE;
    }
    elseif (method_exists($field, 'views_form')) {
      $field->views_form($form, $form_state);
      $has_form = TRUE;
    }

    // Build the substitutions array for use in the theme function.
    if ($has_form) {
      foreach ($view->result as $row_id => $row) {
        if ($method_form_element_row_id_exists) {
          $form_element_row_id = $field->form_element_row_id($row_id);
        }
        else {
          $form_element_row_id = $row_id;
        }

        $substitutions[] = array(
          'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
          'field_name' => $form_element_name,
          'row_id' => $form_element_row_id,
        );
      }
    }
  }

  // Give the area handlers a chance to extend the form.
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
  $empty = empty($view->result);
  foreach ($area_handlers as $area) {
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
      $area->views_form($form, $form_state);
    }
  }

  $form['#substitutions'] = array(
    '#type' => 'value',
    '#value' => $substitutions,
  );
  $form['actions'] = array(
    '#type' => 'container',
    '#attributes' => array('class' => array('form-actions')),
    '#weight' => 100,
  );
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );

  return $form;
}

/**
 * Validate handler for the first step of the views form.
 * Calls any existing views_form_validate functions located
 * on the views fields.
 */
function views_form_views_form_validate($form, &$form_state) {
  $view = $form_state['build_info']['args'][0];

  // Call the validation method on every field handler that has it.
  foreach ($view->field as $field_name => $field) {
    if (method_exists($field, 'views_form_validate')) {
      $field->views_form_validate($form, $form_state);
    }
  }

  // Call the validate method on every area handler that has it.
  foreach (array('header', 'footer') as $area) {
    foreach ($view->{$area} as $area_name => $area_handler) {
      if (method_exists($area_handler, 'views_form_validate')) {
        $area_handler->views_form_validate($form, $form_state);
      }
    }
  }
}

/**
 * Submit handler for the first step of the views form.
 * Calls any existing views_form_submit functions located
 * on the views fields.
 */
function views_form_views_form_submit($form, &$form_state) {
  $view = $form_state['build_info']['args'][0];

  // Call the submit method on every field handler that has it.
  foreach ($view->field as $field_name => $field) {
    if (method_exists($field, 'views_form_submit')) {
      $field->views_form_submit($form, $form_state);
    }
  }

  // Call the submit method on every area handler that has it.
  foreach (array('header', 'footer') as $area) {
    foreach ($view->{$area} as $area_name => $area_handler) {
      if (method_exists($area_handler, 'views_form_submit')) {
        $area_handler->views_form_submit($form, $form_state);
      }
    }
  }
}

// ------------------------------------------------------------------
// Exposed widgets form

/**
 * Form builder for the exposed widgets form.
 *
 * Be sure that $view and $display are references.
 */
function views_exposed_form($form, &$form_state) {
  // Don't show the form when batch operations are in progress.
  if ($batch = batch_get() && isset($batch['current_set'])) {
    return array(
      // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
      '#theme' => '',
    );
  }

  // Make sure that we validate because this form might be submitted
  // multiple times per page.
  $form_state['must_validate'] = TRUE;
  $view = &$form_state['view'];
  $display = &$form_state['display'];

  $form_state['input'] = $view->get_exposed_input();

  // Let form plugins know this is for exposed widgets.
  $form_state['exposed'] = TRUE;
  // Check if the form was already created
  if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
    return $cache;
  }

  $form['#info'] = array();

  if (!variable_get('clean_url', FALSE)) {
    $form['q'] = array(
      '#type' => 'hidden',
      '#value' => $view->get_url(),
    );
  }

  // Go through each handler and let it generate its exposed widget.
  foreach ($view->display_handler->handlers as $type => $value) {
    foreach ($view->$type as $id => $handler) {
      if ($handler->can_expose() && $handler->is_exposed()) {
        $handler->exposed_form($form, $form_state);
        if ($info = $handler->exposed_info()) {
          $form['#info']["$type-$id"] = $info;
        }
      }
    }
  }

  $form['submit'] = array(
    '#name' => '', // prevent from showing up in $_GET.
    '#type' => 'submit',
    '#value' => t('Apply'),
    '#id' => drupal_html_id('edit-submit-' . $view->name),
  );

  $form['#action'] = url($view->get_url());
  $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
  $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
//  $form['#attributes']['class'] = array('views-exposed-form');

  // If using AJAX, we need the form plugin.
  if ($view->use_ajax) {
    drupal_add_library('system', 'jquery.form');
  }
  ctools_include('dependent');

  $exposed_form_plugin = $form_state['exposed_form_plugin'];
  $exposed_form_plugin->exposed_form_alter($form, $form_state);

  // Save the form
  views_exposed_form_cache($view->name, $view->current_display, $form);

  return $form;
}

/**
 * Implement hook_form_alter for the exposed form.
 *
 * Since the exposed form is a GET form, we don't want it to send a wide
 * variety of information.
 */
function views_form_views_exposed_form_alter(&$form, &$form_state) {
  $form['form_build_id']['#access'] = FALSE;
  $form['form_token']['#access'] = FALSE;
  $form['form_id']['#access'] = FALSE;
}

/**
 * Validate handler for exposed filters
 */
function views_exposed_form_validate(&$form, &$form_state) {
  foreach (array('field', 'filter') as $type) {
    $handlers = &$form_state['view']->$type;
    foreach ($handlers as $key => $handler) {
      $handlers[$key]->exposed_validate($form, $form_state);
    }
  }
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
  $exposed_form_plugin->exposed_form_validate($form, $form_state);
}

/**
 * Submit handler for exposed filters
 */
function views_exposed_form_submit(&$form, &$form_state) {
  foreach (array('field', 'filter') as $type) {
    $handlers = &$form_state['view']->$type;
    foreach ($handlers as $key => $info) {
      $handlers[$key]->exposed_submit($form, $form_state);
    }
  }
  $form_state['view']->exposed_data = $form_state['values'];
  $form_state['view']->exposed_raw_input = array();


  $exclude = array('q', 'submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
  $exposed_form_plugin->exposed_form_submit($form, $form_state, $exclude);

  foreach ($form_state['values'] as $key => $value) {
    if (!in_array($key, $exclude)) {
      $form_state['view']->exposed_raw_input[$key] = $value;
    }
  }
}

/**
 * Save the Views exposed form for later use.
 *
 * @param $views_name
 *   String. The views name.
 * @param $display_name
 *   String. The current view display name.
 * @param $form_output
 *   Array (optional). The form structure. Only needed when inserting the value.
 * @return
 *   Array. The form structure, if any. Otherwise, return FALSE.
 */
function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
  static $views_exposed;

  // Save the form output
  if (!empty($form_output)) {
    $views_exposed[$views_name][$display_name] = $form_output;
    return;
  }

  // Return the form output, if any
  return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
}

// ------------------------------------------------------------------
// Misc helpers

/**
 * Build a list of theme function names for use most everywhere.
 */
function views_theme_functions($hook, $view, $display = NULL) {
  require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'views') . "/theme/theme.inc";
  return _views_theme_functions($hook, $view, $display);
}

/**
 * Substitute current time; this works with cached queries.
 */
function views_views_query_substitutions($view) {
  global $language_content;
  return array(
    '***CURRENT_VERSION***' => VERSION,
    '***CURRENT_TIME***' => REQUEST_TIME,
    '***CURRENT_LANGUAGE***' => $language_content->language,
    '***DEFAULT_LANGUAGE***' => language_default('language'),
  );
}

/**
 * Implements hook_query_TAG_alter().
 *
 * This is the hook_query_alter() for queries tagged by Views and is used to
 * add in substitutions from hook_views_query_substitutions().
 */
function views_query_views_alter(QueryAlterableInterface $query) {
  $substitutions = $query->getMetaData('views_substitutions');
  $tables =& $query->getTables();
  $where =& $query->conditions();

  // Replaces substitions in tables.
  foreach ($tables as $table_name => $table_metadata) {
    foreach ($table_metadata['arguments'] as $replacement_key => $value) {
      if (isset($substitutions[$value])) {
        $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
      }
    }
  }

  // Replaces substitions in filter criterias.
  _views_query_tag_alter_condition($query, $where, $substitutions);
}

/**
 * Replaces the substitutions recursive foreach condition.
 */
function _views_query_tag_alter_condition(QueryAlterableInterface $query, &$conditions, $substitutions) {
  foreach ($conditions as $condition_id => &$condition) {
    if (is_numeric($condition_id)) {
      if (is_string($condition['field'])) {
        $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
      }
      elseif (is_object($condition['field'])) {
        $sub_conditions =& $condition['field']->conditions();
        _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
      }
      // $condition['value'] is a subquery so alter the subquery recursive.
      // Therefore take sure to get the metadata of the main query.
      if (is_object($condition['value'])) {
        $subquery = $condition['value'];
        $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
        views_query_views_alter($condition['value']);
      }
      elseif (isset($condition['value'])) {
        $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
      }
    }
  }
}

/**
 * Embed a view using a PHP snippet.
 *
 * This function is meant to be called from PHP snippets, should one wish to
 * embed a view in a node or something. It's meant to provide the simplest
 * solution and doesn't really offer a lot of options, but breaking the function
 * apart is pretty easy, and this provides a worthwhile guide to doing so.
 *
 * Note that this function does NOT display the title of the view. If you want
 * to do that, you will need to do what this function does manually, by
 * loading the view, getting the preview and then getting $view->get_title().
 *
 * @param $name
 *   The name of the view to embed.
 * @param $display_id
 *   The display id to embed. If unsure, use 'default', as it will always be
 *   valid. But things like 'page' or 'block' should work here.
 * @param ...
 *   Any additional parameters will be passed as arguments.
 */
function views_embed_view($name, $display_id = 'default') {
  $args = func_get_args();
  array_shift($args); // remove $name
  if (count($args)) {
    array_shift($args); // remove $display_id
  }

  $view = views_get_view($name);
  if (!$view || !$view->access($display_id)) {
    return;
  }

  return $view->preview($display_id, $args);
}

/**
* Get the result of a view.
*
* @param string $name
*      The name of the view to retrieve the data from.
* @param string $display_id
*      The display id. On the edit page for the view in question, you'll find
*      a list of displays at the left side of the control area. "Master"
*      will be at the top of that list. Hover your cursor over the name of the
*      display you want to use. An URL will appear in the status bar of your
*      browser. This is usually at the bottom of the window, in the chrome.
*      Everything after #views-tab- is the display ID, e.g. page_1.
 * @param ...
 *   Any additional parameters will be passed as arguments.
* @return
*      array
*          An array containing an object for each view item.
*/
function views_get_view_result($name, $display_id = NULL) {
  $args = func_get_args();
  array_shift($args); // remove $name
  if (count($args)) {
    array_shift($args); // remove $display_id
  }

  $view = views_get_view($name);
  if (is_object($view)) {
    if (is_array($args)) {
      $view->set_arguments($args);
    }
    if (is_string($display_id)) {
      $view->set_display($display_id);
    }
    else {
      $view->init_display();
    }
    $view->pre_execute();
    $view->execute();
    return $view->result;
  }
  else {
    return array();
  }
}

/**
 * Export a field.
 */
function views_var_export($var, $prefix = '', $init = TRUE) {
  if (is_array($var)) {
    if (empty($var)) {
      $output = 'array()';
    }
    else {
      $output = "array(\n";
      foreach ($var as $key => $value) {
        $output .= "  " . views_var_export($key, '', FALSE) . " => " . views_var_export($value, '  ', FALSE) . ",\n";
      }
      $output .= ')';
    }
  }
  elseif (is_bool($var)) {
    $output = $var ? 'TRUE' : 'FALSE';
  }
  elseif (is_string($var) && strpos($var, "\n") !== FALSE) {
    // Replace line breaks in strings with a token for replacement
    // at the very end. This protects multi-line strings from
    // unintentional indentation.
    $var = str_replace("\n", "***BREAK***", $var);
    $output = var_export($var, TRUE);
  }
  else {
    $output = var_export($var, TRUE);
  }

  if ($prefix) {
    $output = str_replace("\n", "\n$prefix", $output);
  }

  if ($init) {
    $output = str_replace("***BREAK***", "\n", $output);
  }

  return $output;
}

/**
 * Prepare a string for use as a valid CSS identifier (element, class or ID name).
 * This function is similar to a core version but with more sane filter values.
 *
 * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
 * CSS identifiers (including element names, classes, and IDs in selectors.)
 *
 * @param $identifier
 *   The identifier to clean.
 * @param $filter
 *   An array of string replacements to use on the identifier.
 * @return
 *   The cleaned identifier.
 *
 * @see drupal_clean_css_identifier
 */
function views_clean_css_identifier($identifier, $filter = array(' ' => '-', '/' => '-', '[' => '-', ']' => '')) {
  // By default, we filter using Drupal's coding standards.
  $identifier = strtr($identifier, $filter);

  // Valid characters in a CSS identifier are:
  // - the hyphen (U+002D)
  // - a-z (U+0030 - U+0039)
  // - A-Z (U+0041 - U+005A)
  // - the underscore (U+005F)
  // - 0-9 (U+0061 - U+007A)
  // - ISO 10646 characters U+00A1 and higher
  // We strip out any character not in the above list.
  $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);

  return $identifier;
}

/**
 * Implement hook_views_exportables().
 */
function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
  $all_views = views_get_all_views();
  if ($op == 'list') {

    foreach ($all_views as $name => $view) {
      // in list, $views is a list of tags.
      if (empty($views) || in_array($view->tag, $views)) {
        $return[$name] = array(
          'name' => check_plain($name),
          'desc' => check_plain($view->description),
          'tag' => check_plain($view->tag)
        );
      }
    }
    return $return;
  }

  if ($op == 'export') {
    $code = "/**\n";
    $code .= " * Implement hook_views_default_views().\n";
    $code .= " */\n";
    $code .= "function " . $name . "_views_default_views() {\n";
    foreach ($views as $view => $truth) {
      $code .= "  /*\n";
      $code .= "   * View " . var_export($all_views[$view]->name, TRUE) . "\n";
      $code .= "   */\n";
      $code .= $all_views[$view]->export('  ');
      $code .= '  $views[$view->name] = $view;' . "\n\n";
    }
    $code .= "  return \$views;\n";
    $code .= "}\n";

    return $code;
  }
}

/**
 * #process callback to see if we need to check_plain() the options.
 *
 * Since FAPI is inconsistent, the #options are sanitized for you in all cases
 * _except_ checkboxes. We have form elements that are sometimes 'select' and
 * sometimes 'checkboxes', so we need decide late in the form rendering cycle
 * if the options need to be sanitized before they're rendered. This callback
 * inspects the type, and if it's still 'checkboxes', does the sanitation.
 */
function views_process_check_options($element, &$form_state) {
  if ($element['#type'] == 'checkboxes' || $element['#type'] == 'checkbox') {
    $element['#options'] = array_map('check_plain', $element['#options']);
  }
  return $element;
}

/**
 * Trim the field down to the specified length.
 *
 * @param $alter
 *   - max_length: Maximum lenght of the string, the rest gets truncated.
 *   - word_boundary: Trim only on a word boundary.
 *   - ellipsis: Show an ellipsis (...) at the end of the trimmed string.
 *   - html: Take sure that the html is correct.
 *
 * @param $value
 *   The string which should be trimmed.
 */
function views_trim_text($alter, $value) {
  if (drupal_strlen($value) > $alter['max_length']) {
    $value = drupal_substr($value, 0, $alter['max_length']);
    // TODO: replace this with cleanstring of ctools
    if (!empty($alter['word_boundary'])) {
      $regex = "(.*)\b.+";
      if (function_exists('mb_ereg')) {
        mb_regex_encoding('UTF-8');
        $found = mb_ereg($regex, $value, $matches);
      }
      else {
        $found = preg_match("/$regex/us", $value, $matches);
      }
      if ($found) {
        $value = $matches[1];
      }
    }
    // Remove scraps of HTML entities from the end of a strings
    $value = rtrim(preg_replace('/(?:<(?!.+>)|&(?!.+;)).*$/us', '', $value));

    if (!empty($alter['ellipsis'])) {
      $value .= t('...');
    }
  }
  if (!empty($alter['html'])) {
    $value = _filter_htmlcorrector($value);
  }

  return $value;
}

/**
 * Adds one to each key of the array.
 *
 * For example array(0 => 'foo') would be array(1 => 'foo').
 */
function views_array_key_plus($array) {
  $keys = array_keys($array);
  rsort($keys);
  foreach ($keys as $key) {
    $array[$key+1] = $array[$key];
    unset($array[$key]);
  }
  asort($array);
  return $array;
}

/**
 * Report to CTools that we use hook_views_api instead of hook_ctools_plugin_api()
 */
function views_ctools_plugin_api_hook_name() {
  return 'views_api';
}

// Declare API compatibility on behalf of core modules:

/**
 * Implements hook_views_api().
 *
 * This one is used as the base to reduce errors when updating.
 */
function views_views_api() {
  return array(
    // in your modules do *not* use views_api_version()!!!
    'api' => views_api_version(),
    'path' => drupal_get_path('module', 'views') . '/modules',
  );
}

if (!function_exists('aggregator_views_api')) {
  function aggregator_views_api() { return views_views_api(); }
}

if (!function_exists('book_views_api')) {
  function book_views_api() { return views_views_api(); }
}

if (!function_exists('comment_views_api')) {
  function comment_views_api() { return views_views_api(); }
}

if (!function_exists('field_views_api')) {
  function field_views_api() { return views_views_api(); }
}

if (!function_exists('file_views_api')) {
  function file_views_api() { return views_views_api(); }
}

if (!function_exists('filter_views_api')) {
  function filter_views_api() { return views_views_api(); }
}

if (!function_exists('image_views_api')) {
  function image_views_api() { return views_views_api(); }
}

if (!function_exists('locale_views_api')) {
  function locale_views_api() { return views_views_api(); }
}

if (!function_exists('node_views_api')) {
  function node_views_api() { return views_views_api(); }
}

if (!function_exists('poll_views_api')) {
  function poll_views_api() { return views_views_api(); }
}

if (!function_exists('profile_views_api')) {
  function profile_views_api() { return views_views_api(); }
}

if (!function_exists('search_views_api')) {
  function search_views_api() { return views_views_api(); }
}

if (!function_exists('statistics_views_api')) {
  function statistics_views_api() { return views_views_api(); }
}

if (!function_exists('system_views_api')) {
  function system_views_api() { return views_views_api(); }
}

if (!function_exists('taxonomy_views_api')) {
  function taxonomy_views_api() { return views_views_api(); }
}

if (!function_exists('translation_views_api')) {
  function translation_views_api() { return views_views_api(); }
}

if (!function_exists('upload_views_api')) {
  function upload_views_api() { return views_views_api(); }
}

if (!function_exists('user_views_api')) {
  function user_views_api() { return views_views_api(); }
}

if (!function_exists('contact_views_api')) {
  function contact_views_api() { return views_views_api(); }
}
