Newer
Older
* 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.
* Implementation of hook_theme(). Register views theming functions.
function views_theme() {
$path = drupal_get_path('module', 'views');
require_once "./$path/theme/theme.inc";
// Some quasi clever array merging here.
// Our extra version of pager from pager.inc
$hooks['views_mini_pager'] = $base + array(
'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()),
);
$arguments = array(
'display' => array('view' => NULL),
'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL),
);
// Default view themes
$hooks['views_view_field'] = $base + array(
'pattern' => 'views_view_field__',
'arguments' => array('view' => NULL, 'field' => NULL, 'row' => 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'])) {
$hooks[$def['theme']] = array(
'pattern' => $def['theme'] . '__',
'path' => $def['path'],
'arguments' => $arguments[$type],
);
if (!function_exists('theme_' . $def['theme'])) {
$hooks[$def['theme']]['template'] = views_css_safe($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['file'],
'path' => $def['path'],
'arguments' => $arguments[$theme_type],
);
if (!function_exists('theme_' . $theme)) {
$hooks[$theme]['template'] = views_css_safe($theme);
}
}
}
Earl Miles
committed
Earl Miles
committed
$hooks['views_exposed_form'] = $base + array(
'template' => 'views-exposed-form',
'pattern' => 'views_exposed_form__',
'arguments' => array('form' => NULL),
);
$hooks['views_more'] = $base + array(
'template' => 'views-more',
'pattern' => 'views_more__',
'arguments' => array('more_url' => NULL),
);
Earl Miles
committed
}
Earl Miles
committed
/**
* Implementation of hook_menu().
Earl Miles
committed
*/
function views_menu() {
Earl Miles
committed
// Any event which causes a menu_rebuild could potentially mean that the
// Views data is updated -- module changes, profile changes, etc.
views_invalidate_cache();
Earl Miles
committed
$items = array();
$items['views/ajax'] = array(
'title' => 'Views',
'page callback' => 'views_ajax',
'file' => 'includes/ajax.inc',
'access callback' => 'user_access',
'access arguments' => array('access content'),
'description' => 'Ajax callback for view loading.',
'type' => MENU_CALLBACK,
);
Earl Miles
committed
return $items;
}
Earl Miles
committed
/**
* Implementation of hook_menu_alter().
*/
function views_menu_alter(&$callbacks) {
$our_paths = array();
foreach (views_get_applicable_views('uses hook menu') as $data) {
Earl Miles
committed
list($view, $display_id) = $data;
$result = $view->execute_hook_menu($display_id);
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.
Earl Miles
committed
$regex = '#^('. preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) .')$#';
Earl Miles
committed
$matches = preg_grep($regex, array_keys($callbacks));
// Remove any conflicting items that were found.
Earl Miles
committed
foreach ($matches as $path) {
// Don't remove the paths we just added!
if (!isset($our_paths[$path])) {
Earl Miles
committed
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.
Earl Miles
committed
$callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
$callbacks[$path]['page arguments'][1][] = $display_id;
$callbacks[$path]['access arguments'][0][1] = (array)$callbacks[$path]['access arguments'][0][1];
$callbacks[$path]['access arguments'][0][1][] = $display_id;
$callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
$callbacks[$path]['load arguments'][1][] = $display_id;
}
$our_paths[$path] = TRUE;
Earl Miles
committed
}
}
}
}
Earl Miles
committed
/**
Earl Miles
committed
* Helper function for menu loading. This will automatically be
Earl Miles
committed
* 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 $index
Earl Miles
committed
* The menu argument index. This counts from 1.
Earl Miles
committed
*/
function views_arg_load($value, $name, $display_id, $index) {
Earl Miles
committed
if ($view = views_get_view($name)) {
Earl Miles
committed
$view->init_handlers();
$ids = array_keys($view->argument);
$indexes = array();
$path = explode('/', $view->get_url());
foreach ($path as $id => $piece) {
if ($piece == '%' && !empty($ids)) {
$indexes[$id] = array_shift($ids);
}
}
if (isset($indexes[$index])) {
if (isset($view->argument[$indexes[$index]])) {
Earl Miles
committed
return $view->argument[$indexes[$index]]['handler']->validate_argument($value) ? $value : NULL;
Earl Miles
committed
}
}
}
/**
* 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
Earl Miles
committed
if ($view = views_get_view($name)) {
Earl Miles
committed
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();
}
/**
* Implementation of hook_block
*/
function views_block($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'list':
$items = array();
foreach (views_get_applicable_views('uses hook block') as $data) {
Earl Miles
committed
list($view, $display_id) = $data;
Earl Miles
committed
$result = $view->execute_hook_block($display_id);
if (is_array($result)) {
Earl Miles
committed
}
Earl Miles
committed
}
Earl Miles
committed
// 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]);
}
}
variable_set('views_block_hashes', $hashes);
Earl Miles
committed
return $items;
case 'view':
Earl Miles
committed
$start = microtime();
Earl Miles
committed
// 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];
}
}
Earl Miles
committed
list($name, $display_id) = explode('-', $delta);
// Load the view
Earl Miles
committed
if ($view = views_get_view($name)) {
if ($view->access($display_id)) {
Earl Miles
committed
$output = $view->execute_display($display_id);
vpr("Block $view->name execute time: " . (microtime() - $start) * 1000 . "ms");
return $output;
Earl Miles
committed
}
break;
}
}
* Implementation of hook_flush_caches().
return array('cache_views');
}
/**
* Invalidate the views cache, forcing a rebuild on the next grab of table data.
*/
function views_invalidate_cache() {
cache_clear_all('*', 'cache_views', true);
}
Earl Miles
committed
/**
* Determine if the given user has access to the view + display.
*
* @param $view
* May be a view object, or an array with the view name and the display ID,
* or a string to use as the view name.
* @param $account
* An optional account to use; if left off, the current user will be used.
Earl Miles
committed
*/
function views_access($view, $account = NULL) {
if (is_array($view)) {
list($name, $display_id) = $view;
Earl Miles
committed
$view = views_get_view($name);
if (!$view) {
Earl Miles
committed
}
elseif (is_string($view)) {
Earl Miles
committed
$view = views_get_view($view);
if (!$view) {
Earl Miles
committed
return FALSE;
}
$display_id = 'default';
}
else {
Earl Miles
committed
// Clone the view to prevent problems.
$view = $view->clone_view();
$display_id = isset($view->current_display) ? $view->current_display : 'default';
Earl Miles
committed
}
return $view->access($display_id, $account);
Earl Miles
committed
}
// ------------------------------------------------------------------
// Functions to help identify views that are running or ran
Earl Miles
committed
/**
* 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.
*/
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
*/
Earl Miles
committed
function &views_set_current_view($view = NULL) {
static $cache = NULL;
if (isset($view)) {
Earl Miles
committed
$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.
*/
function &views_get_current_view() {
return views_set_current_view();
}
// ------------------------------------------------------------------
// Include file helpers
/**
* Include views .inc files as necessary.
*/
function views_include($file) {
static $used = array();
if (!isset($used[$file])) {
require_once './' . drupal_get_path('module', 'views') . "/includes/$file.inc";
}
$used[$file] = TRUE;
/**
* Load views files on behalf of modules.
*/
function views_module_include($file) {
$views_path = drupal_get_path('module', 'views') . '/modules';
foreach (module_list() as $module) {
$module_path = drupal_get_path('module', $module);
if (file_exists("$module_path/$module.$file")) {
require_once "./$module_path/$module.$file";
}
else if (file_exists("$module_path/includes/$module.$file")) {
require_once "./$module_path/includes/$module.$file";
}
else if (file_exists("$views_path/$module.$file")) {
require_once "./$views_path/$module.$file";
}
}
}
/**
* Include views .css files.
*/
function views_add_css($file) {
drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css");
}
/**
* Include views .js files.
*/
function views_add_js($file) {
static $base = TRUE;
if ($base) {
drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
}
drupal_add_js(drupal_get_path('module', 'views') . "/js/$file.js");
}
/**
* Load views files on behalf of modules.
*/
function views_include_handlers() {
static $finished = FALSE;
// Ensure this only gets run once.
if ($finished) {
return;
}
Earl Miles
committed
views_include('base');
Earl Miles
committed
views_include('join.handlers');
views_include('relationship.handlers');
views_include('field.handlers');
views_include('sort.handlers');
views_include('filter.handlers');
views_include('argument.handlers');
views_include('cache');
_views_include_handlers();
/**
* Load default views files on behalf of modules.
*/
function views_include_default_views() {
static $finished = FALSE;
// Ensure this only gets run once.
if ($finished) {
return;
}
// Default views hooks may be in the normal handler file,
// or in a separate views_default file at the discretion of
Earl Miles
committed
// the module author.
views_include_handlers();
Earl Miles
committed
_views_include_default_views();
$finished = TRUE;
}
// -----------------------------------------------------------------------
// Views handler functions
/**
* Fetch a handler from the data cache.
*/
function views_get_handler($table, $field, $key) {
$data = views_fetch_data($table);
if (isset($data[$field][$key])) {
return _views_prepare_handler($data[$field][$key], $data, $field);
}
// DEBUG -- identify missing handlers
Earl Miles
committed
vpr("Missing handler: $table $field $key");
Earl Miles
committed
return new views_handler_broken;
/**
* Fetch Views' data from the cache
*/
function views_fetch_data($table = NULL) {
views_include('cache');
return _views_fetch_data($table);
// -----------------------------------------------------------------------
// Views plugin functions
/**
* Fetch the plugin data from cache.
*/
function views_fetch_plugin_data($type = NULL, $plugin = NULL) {
views_include('cache');
return _views_fetch_plugin_data($type, $plugin);
* Get a handler for a plugin
function views_get_plugin($type, $plugin) {
$definition = views_fetch_plugin_data($type, $plugin);
if (!empty($definition)) {
return _views_create_handler($definition);
}
// -----------------------------------------------------------------------
// Views database functions
/**
* Get a view from the default views defined by modules.
Earl Miles
committed
*
* Default views are cached per-language. This function will rescan the
* default_views hook if necessary.
*
* @param $view_name
* The name of the view to load.
* @return
* A view object or NULL if it is not available.
*/
function &views_get_default_view($view_name) {
$cache = views_discover_default_views();
Earl Miles
committed
if (isset($cache[$view_name])) {
return $cache[$view_name];
}
return $null;
}
/**
* Create an empty view to work with.
*
* @return
* 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';
}
/**
* Scan all modules for default views and rebuild the default views cache.
*
* @return An associative array of all known default views.
*/
function views_discover_default_views() {
Earl Miles
committed
static $cache = array();
Earl Miles
committed
if (empty($cache)) {
views_include('cache');
$cache = _views_discover_default_views();
Earl Miles
committed
}
return $cache;
}
Earl Miles
committed
/**
* Return a list of all views and display IDs that have a particular
* setting in their display's plugin settings.
Earl Miles
committed
*
* @return
Earl Miles
committed
* @code
Earl Miles
committed
* array(
* array($view, $display_id),
* array($view, $display_id),
* );
Earl Miles
committed
*/
function views_get_applicable_views($type) {
Earl Miles
committed
// @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();
Earl Miles
committed
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])) {
Earl Miles
committed
// This view uses hook menu. Clone it so that different handlers
// don't trip over each other, and add it to the list.
Earl Miles
committed
$v = $view->clone_view();
if ($v->set_display($id)) {
$result[] = array($v, $id);
Earl Miles
committed
// 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);
Earl Miles
committed
}
}
}
return $result;
}
/**
* Return an array of all views as fully loaded $view objects.
function views_get_all_views() {
static $views = array();
Earl Miles
committed
if (empty($views)) {
// First, get all applicable views.
views_include('view');
$views = view::load_views();
// Get all default views.
$status = variable_get('views_defaults', array());
foreach (views_discover_default_views() as $view) {
// Determine if default view is enabled or disabled.
if (isset($status[$view->name])) {
$view->disabled = $status[$view->name];
}
// If overridden, also say so.
if (!empty($views[$view->name])) {
$views[$view->name]->type = t('Overridden');
}
else {
$view->type = t('Default');
$views[$view->name] = $view;
}
}
}
return $views;
}
/**
* 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) {
views_include('view');
$view = view::load($name, $reset);
$default_view = views_get_default_view($name);
if (empty($view) && empty($default_view)) {
return;
}
elseif (empty($view) && !empty($default_view)) {
$default_view->type = t('Default');
return $default_view->clone_view();
}
elseif (!empty($view) && !empty($default_view)) {
$view->type = t('Overridden');
}
return $view->clone_view();
}
Earl Miles
committed
// ------------------------------------------------------------------
// Views debug helper functions
Earl Miles
committed
/**
* Provide debug output for Views. This relies on devel.module
*/
function views_debug($message) {
if (module_exists('devel') && variable_get('views_devel_output', FALSE)) {
drupal_set_content(variable_get('views_devel_region', 'footer'), dpr($message, TRUE));
Earl Miles
committed
}
}
/**
* Shortcut to views_debug()
*/
function vpr($message) {
views_debug($message);
}
/**
* Debug messages
*/
function vsm($message) {
if (module_exists('devel')) {
dsm($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());
}
// ------------------------------------------------------------------
// Exposed widgets form
Earl Miles
committed
/**
* Form builder for the exposed widgets form.
*
* Be sure that $view and $display are references.
*/
Earl Miles
committed
function views_exposed_form(&$form_state) {
$view = &$form_state['view'];
$display = &$form_state['display'];
// Fill our input either from $_GET or from something previously set on the
Earl Miles
committed
// view.
if (empty($view->exposed_input)) {
$input = $_GET;
// unset items that are definitely not our input:
unset($input['page']);
unset($input['q']);
// If we have no input at all, check for remembered input via session.
if (empty($input) && !empty($_SESSION['views'][$view->name][$view->current_display])) {
$input = $_SESSION['views'][$view->name][$view->current_display];
}
$form['#post'] = $input;
Earl Miles
committed
else {
$form['#post'] = $view->exposed_input;
}
// Let form plugins know this is for exposed widgets.
$form_state['exposed'] = TRUE;
if (!variable_get('clean_url', FALSE)) {
$form['q'] = array(
'#type' => 'hidden',
'#value' => $view->get_url(),
);
}
// Go through each filter and let it generate its info.
foreach ($view->filter as $id => $filter) {
$filter['handler']->exposed_form($form, $form_state);
if ($info = $filter['handler']->exposed_info()) {
$form['#info']['filter-' . $id] = $info;
}
}
// @todo deal with exposed sorts
$form['submit'] = array(
'#name' => '', // prevent from showing up in $_GET.
'#type' => 'submit',
'#value' => t('Apply'),
);
$form['#action'] = url($view->get_url());
$form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
// If using AJAX, we need the form plugin.
if ($view->use_ajax) {
drupal_add_js('misc/jquery.form.js');
}
Earl Miles
committed
views_add_js('dependent');
/**
* Validate handler for exposed filters
*/
function views_exposed_form_validate(&$form, &$form_state) {
foreach (array('field', 'filter') as $type) {
$var = &$form_state['view']->$type;
foreach ($var as $key => $info) {
$var[$key]['handler']->exposed_validate($form, $form_state);
}
}
}
/**
* Submit handler for exposed filters
*/
function views_exposed_form_submit(&$form, &$form_state) {
foreach (array('field', 'filter') as $type) {
$var = &$form_state['view']->$type;
foreach ($var as $key => $info) {
$var[$key]['handler']->exposed_submit($form, $form_state);
Earl Miles
committed
$form_state['view']->exposed_data = $form_state['values'];
Earl Miles
committed
$form_state['view']->exposed_raw_input = array();
foreach ($form_state['values'] as $key => $value) {
if (!in_array($key, array('q', 'submit', 'form_build_id', 'form_id', 'form_token'))) {
$form_state['view']->exposed_raw_input[$key] = $value;
}
}
// ------------------------------------------------------------------
// Misc helpers
Earl Miles
committed
* Build a list of theme function names for use most everywhere.
Earl Miles
committed
function views_theme_functions($hook, $view, $display = NULL) {
require_once './' . drupal_get_path('module', 'views') . "/theme/theme.inc";
Earl Miles
committed
return _views_theme_functions($hook, $view, $display);
Earl Miles
committed
* Views' replacement for drupal_get_form so that we can do more with
* less.
*
* Items that can be set on the form_state include:
* - input: The source of input. If unset this will be $_POST.
* - no_redirect: Absolutely do not redirect the form even if instructed
* to do so.
* - rerender: If no_redirect is set and the form was successfully submitted,
Earl Miles
committed
* rerender the form. Otherwise it will just return.
*
Earl Miles
committed
function drupal_build_form($form_id, &$form_state) {
views_include('form');
return _drupal_build_form($form_id, $form_state);
Earl Miles
committed
/**
* Substitute current time; this works with cached queries.
*/
function views_views_query_substitutions($view) {
global $language;
return array(
'***CURRENT_TIME***' => time(),
'***CURRENT_LANGUAGE***' => $language->language,
'***NO_LANGUAGE***' => '',
);
Earl Miles
committed
}
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
/**
* 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.
*
* @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) {
return;
}
return $view->preview($display_id, $args);
}
Earl Miles
committed
/**
* Export a field.
*/
function views_var_export($var, $prefix = '') {
if (is_array($var)) {
if (empty($var)) {
$output = 'array()';
}
else {
$output = "array(\n";
foreach ($var as $key => $value) {
$output .= " '$key' => " . views_var_export($value, ' ') . ",\n";
Earl Miles
committed
}
$output .= ')';
}
}
else if (is_bool($var)) {
$output = $var ? 'TRUE' : 'FALSE';
}
else {
$output = var_export($var, TRUE);
}
if ($prefix) {
$output = str_replace("\n", "\n$prefix", $output);
}
return $output;
}
/**
* Prepare the specified string for use as a CSS identifier.
*/
function views_css_safe($string) {
return str_replace('_', '-', $string);
}