<?php// $Id$/** * @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 */functionviews_api_version(){return2.0;}/** * Implementation of hook_theme(). Register views theming functions. */functionviews_theme(){$path=drupal_get_path('module','views');require_once"./$path/theme/theme.inc";// 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('arguments'=>array('tags'=>array(),'limit'=>10,'element'=>0,'parameters'=>array()),'pattern'=>'views_mini_pager__',);$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 pluginsforeach($pluginsas$type=>$info){foreach($infoas$plugin=>$def){if(isset($def['theme'])){$hooks[$def['theme']]=array('pattern'=>$def['theme'].'__','file'=>$def['theme file'],'path'=>$def['theme 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['theme file'],'path'=>$def['theme path'],'arguments'=>$arguments[$theme_type],);if(!function_exists('theme_'.$theme)){$hooks[$theme]['template']=views_css_safe($theme);}}}}}$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),);return$hooks;}/** * 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. * * @todo Remove this in Drupal 7, as it has a code registry. */functionviews_preprocess_node(&$vars){// The 'view' attribute of the node is added in template_preprocess_views_view_row_node()if(!empty($vars['node']->view)&&!empty($vars['node']->view->name)){$vars['template_files'][]='node-view-'.$vars['node']->view->name;if(!empty($vars['node']->view->current_display)){$vars['template_files'][]='node-view-'.$vars['node']->view->name.'-'.$vars['node']->view->current_display;}}}/* * Implementation of hook_perm() */functionviews_ui_perm(){returnarray('access all views');}/** * Implementation of hook_menu(). */functionviews_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','access callback'=>'user_access','access arguments'=>array('access content'),'description'=>'Ajax callback for view loading.','file'=>'includes/ajax.inc','type'=>MENU_CALLBACK,);// Path is not admin/build/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','access callback'=>'user_access','access arguments'=>array('access content'),'file'=>'includes/ajax.inc','type'=>MENU_CALLBACK,);return$items;}/** * Implementation of hook_menu_alter(). */functionviews_menu_alter(&$callbacks){$our_paths=array();$views=views_get_applicable_views('uses hook menu');foreach($viewsas$data){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.$regex='#^('.preg_replace('#%views_arg#','%[^/]*',implode('|',array_keys($result))).')$#';$matches=preg_grep($regex,array_keys($callbacks));// Remove any conflicting items that were found.foreach($matchesas$path){// Don't remove the paths we just added!if(!isset($our_paths[$path])){unset($callbacks[$path]);}}foreach($resultas$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($viewsas$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 $index * The menu argument index. This counts from 1. */functionviews_arg_load($value,$name,$display_id,$index){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($pathas$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();return$arg;}}$view->destroy();}}/** * Page callback entry point; requires a view and a display id, then * passes control to the display handler. */functionviews_page(){$args=func_get_args();$name=array_shift($args);$display_id=array_shift($args);// Load the viewif($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.returndrupal_not_found();}/** * Implementation of hook_block */functionviews_block($op='list',$delta=0,$edit=array()){switch($op){case'list':$items=array();$views=views_get_all_views();foreach($viewsas$view){$view->init_display();foreach($view->displayas$display_id=>$display){if(isset($display->handler)&&!empty($display->handler->definition['uses hook block'])){$result=$display->handler->execute_hook_block();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($keysas$delta){if(strlen($delta)>=32){$hash=md5($delta);$hashes[$hash]=$delta;$items[$hash]=$items[$delta];unset($items[$delta]);}}variable_set('views_block_hashes',$hashes);// Save memory: Destroy those views.foreach($viewsas$view){$view->destroy();}return$items;case'view':$start=microtime();// 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);$view->destroy();return$output;}}$view->destroy();}}list($name,$display_id)=explode('-',$delta);// Load the viewif($view=views_get_view($name)){if($view->access($display_id)){$output=$view->execute_display($display_id);vpr("Block $view->name execute time: ".(microtime()-$start)*1000."ms");$view->destroy();return$output;}$view->destroy();}break;}}/** * Implementation of hook_flush_caches(). */functionviews_flush_caches(){returnarray('cache_views');}/** * Invalidate the views cache, forcing a rebuild on the next grab of table data. */functionviews_invalidate_cache(){cache_clear_all('*','cache_views',true);}/** * 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. */functionviews_access(){if(user_access('access all views')){returnTRUE;}$args=func_get_args();foreach($argsas$arg){if($arg===TRUE){returnTRUE;}if(!is_array($arg)){continue;}list($callback,$arguments)=$arg;if(function_exists($callback)&&call_user_func_array($callback,$arguments)){returnTRUE;}}returnFALSE;}/** * Access callback to determine if the logged in user has any of the * requested roles. * * This must be in views.module as it is called by menu access callback * and can be called often. */functionviews_check_roles($rids){global$user;$roles=array_keys($user->roles);$roles[]=$user->uid?DRUPAL_AUTHENTICATED_RID:DRUPAL_ANONYMOUS_RID;returnarray_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. */function&views_get_page_view(){returnviews_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 */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. */function&views_get_current_view(){returnviews_set_current_view();}// ------------------------------------------------------------------// Include file helpers/** * Include views .inc files as necessary. */functionviews_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. */functionviews_module_include($file){$views_path=drupal_get_path('module','views').'/modules';foreach(views_get_module_apis()as$module=>$info){if(file_exists("./$info[path]/$module.$file")){require_once"./$info[path]/$module.$file";}}}/** * Get a list of modules that support the current views API. */functionviews_get_module_apis(){static$cache=NULL;if(!isset($cache)){$cache=array();foreach(module_implements('views_api')as$module){$function=$module.'_views_api';$info=$function();if(isset($info['api'])&&$info['api']==2.000){if(!isset($info['path'])){$info['path']=drupal_get_path('module',$module);}$cache[$module]=$info;}}}return$cache;}/** * Include views .css files. */functionviews_add_css($file){drupal_add_css(drupal_get_path('module','views')."/css/$file.css");}/** * Include views .js files. */functionviews_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;if($base){drupal_add_js(drupal_get_path('module','views')."/js/base.js");$base=FALSE;}drupal_add_js(drupal_get_path('module','views')."/js/$file.js");}/** * Load views files on behalf of modules. */functionviews_include_handlers(){static$finished=FALSE;// Ensure this only gets run once.if($finished){return;}views_include('base');views_include('handlers');views_include('cache');views_include('plugins');_views_include_handlers();$finished=TRUE;}/** * Load default views files on behalf of modules. */functionviews_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// the module author.views_include_handlers();_views_include_default_views();$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 * * @return * An instance of a handler object. May be views_handler_broken. */functionviews_get_handler($table,$field,$key){$data=views_fetch_data($table);if(isset($data[$field][$key])){// Set up a default handler:if(empty($data[$field][$key]['handler'])){$data[$field][$key]['handler']='views_handler_'.$key;}return_views_prepare_handler($data[$field][$key],$data,$field);}// DEBUG -- identify missing handlersvpr("Missing handler: $table$field$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);}/** * Fetch Views' data from the cache */functionviews_fetch_data($table=NULL){views_include('cache');return_views_fetch_data($table);}// -----------------------------------------------------------------------// Views plugin functions/** * Fetch the plugin data from cache. */functionviews_fetch_plugin_data($type=NULL,$plugin=NULL){views_include('cache');return_views_fetch_plugin_data($type,$plugin);}/** * Get a handler for a plugin */functionviews_get_plugin($type,$plugin){$definition=views_fetch_plugin_data($type,$plugin);if(!empty($definition)){return_views_create_handler($definition,$type);}}// -----------------------------------------------------------------------// Views database functions/** * Get a view from the default views defined by modules. * * 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){$null=NULL;$cache=views_discover_default_views();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. */functionviews_new_view(){views_include('view');$view=newview();$view->vid='new';$view->add_display('default');return$view;}/** * Scan all modules for default views and rebuild the default views cache. * * @return An associative array of all known default views. */functionviews_discover_default_views(){static$cache=array();if(empty($cache)){views_include('cache');$cache=_views_discover_default_views();}return$cache;}/** * 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 */functionviews_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($viewsas$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)){$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. */functionviews_get_all_views(){static$views=array();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. */functionviews_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();}// ------------------------------------------------------------------// Views debug helper functions/** * Provide debug output for Views. This relies on devel.module */functionviews_debug($message){if(module_exists('devel')&&variable_get('views_devel_output',FALSE)&&user_access('access devel information')){if(is_string($message)){$output=$message;}else{$output=var_export($message,TRUE);}drupal_set_content(variable_get('views_devel_region','footer'),'<pre>'.$output.'</pre>');}}/** * Shortcut to views_debug() */functionvpr($message){views_debug($message);}/** * Debug messages */functionvsm($message){if(module_exists('devel')){dsm($message);}}functionviews_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;}functionvsm_trace(){vsm(views_trace());}functionvpr_trace(){dpr(views_trace());}// ------------------------------------------------------------------// Exposed widgets form/** * Form builder for the exposed widgets form. * * Be sure that $view and $display are references. */functionviews_exposed_form(&$form_state){// 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;$form['#info']=array();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->filteras$id=>$filter){$view->filter[$id]->exposed_form($form,$form_state);if($info=$view->filter[$id]->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);$form['#id']=views_css_safe('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_js('misc/jquery.form.js');}views_add_js('dependent');return$form;}/** * Validate handler for exposed filters */functionviews_exposed_form_validate(&$form,&$form_state){foreach(array('field','filter')as$type){$handlers=&$form_state['view']->$type;foreach($handlersas$key=>$handler){$handlers[$key]->exposed_validate($form,$form_state);}}}/** * Submit handler for exposed filters */functionviews_exposed_form_submit(&$form,&$form_state){foreach(array('field','filter')as$type){$handlers=&$form_state['view']->$type;foreach($handlersas$key=>$info){$handlers[$key]->exposed_submit($form,$form_state);}}$form_state['view']->exposed_data=$form_state['values'];$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/** * Build a list of theme function names for use most everywhere. */functionviews_theme_functions($hook,$view,$display=NULL){require_once'./'.drupal_get_path('module','views')."/theme/theme.inc";return_views_theme_functions($hook,$view,$display);}/** * 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, * rerender the form. Otherwise it will just return. * */functiondrupal_build_form($form_id,&$form_state){views_include('form');return_drupal_build_form($form_id,$form_state);}/** * Substitute current time; this works with cached queries. */functionviews_views_query_substitutions($view){global$language;returnarray('***CURRENT_TIME***'=>time(),'***CURRENT_LANGUAGE***'=>$language->language,'***DEFAULT_LANGUAGE***'=>language_default('language'),'***NO_LANGUAGE***'=>'',);}/** * 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. */functionviews_embed_view($name,$display_id='default'){$args=func_get_args();array_shift($args);// remove $nameif(count($args)){array_shift($args);// remove $display_id}$view=views_get_view($name);if(!$view){return;}return$view->preview($display_id,$args);}/** * Export a field. */functionviews_var_export($var,$prefix=''){if(is_array($var)){if(empty($var)){$output='array()';}else{$output="array(\n";foreach($varas$key=>$value){$output.=" '$key' => ".views_var_export($value,' ').",\n";}$output.=')';}}elseif(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. */functionviews_css_safe($string){returnstr_replace('_','-',$string);}/** * Implementation of hook_views_exportables(). */functionviews_views_exportables($op='list',$views=NULL,$name='foo'){$all_views=views_get_all_views();if($op=='list'){foreach($all_viewsas$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.=" * Implementation of hook_views_default_views().\n";$code.=" */\n";$code.="function ".$name."_views_default_views() {\n";foreach($viewsas$view=>$truth){$code.=$all_views[$view]->export(' ');$code.=' $views[$view->name] = $view;'."\n\n";}$code.=" return \$views;\n";$code.="}\n";return$code;}}