views.module 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580
  1. <?php
  2. /**
  3. * @file
  4. * Primarily Drupal hooks and global API functions to manipulate views.
  5. *
  6. * This is the main module file for Views. The main entry points into
  7. * this module are views_page() and views_block(), where it handles
  8. * incoming page and block requests.
  9. */
  10. /**
  11. * Advertise the current views api version
  12. */
  13. function views_api_version() {
  14. return '3.0';
  15. }
  16. /**
  17. * Implements hook_forms().
  18. *
  19. * To provide distinct form IDs for Views forms, the View name and
  20. * specific display name are appended to the base ID,
  21. * views_form_views_form. When such a form is built or submitted, this
  22. * function will return the proper callback function to use for the given form.
  23. */
  24. function views_forms($form_id, $args) {
  25. if (strpos($form_id, 'views_form_') === 0) {
  26. return array(
  27. $form_id => array(
  28. 'callback' => 'views_form',
  29. ),
  30. );
  31. }
  32. }
  33. /**
  34. * Returns a form ID for a Views form using the name and display of the View.
  35. */
  36. function views_form_id($view) {
  37. $parts = array(
  38. 'views_form',
  39. $view->name,
  40. $view->current_display,
  41. );
  42. return implode('_', $parts);
  43. }
  44. /**
  45. * Views will not load plugins advertising a version older than this.
  46. */
  47. function views_api_minimum_version() {
  48. return '2';
  49. }
  50. /**
  51. * Implement hook_theme(). Register views theming functions.
  52. */
  53. function views_theme($existing, $type, $theme, $path) {
  54. $path = drupal_get_path('module', 'views');
  55. ctools_include('theme', 'views', 'theme');
  56. // Some quasi clever array merging here.
  57. $base = array(
  58. 'file' => 'theme.inc',
  59. 'path' => $path . '/theme',
  60. );
  61. // Our extra version of pager from pager.inc
  62. $hooks['views_mini_pager'] = $base + array(
  63. 'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array()),
  64. 'pattern' => 'views_mini_pager__',
  65. );
  66. $variables = array(
  67. // For displays, we pass in a dummy array as the first parameter, since
  68. // $view is an object but the core contextual_preprocess() function only
  69. // attaches contextual links when the primary theme argument is an array.
  70. 'display' => array('view_array' => array(), 'view' => NULL),
  71. 'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
  72. 'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
  73. 'exposed_form' => array('view' => NULL, 'options' => NULL),
  74. 'pager' => array(
  75. 'view' => NULL, 'options' => NULL,
  76. 'tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()
  77. ),
  78. );
  79. // Default view themes
  80. $hooks['views_view_field'] = $base + array(
  81. 'pattern' => 'views_view_field__',
  82. 'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
  83. );
  84. $hooks['views_view_grouping'] = $base + array(
  85. 'pattern' => 'views_view_grouping__',
  86. 'variables' => array('view' => NULL, 'grouping' => NULL, 'grouping_level' => NULL, 'rows' => NULL, 'title' => NULL),
  87. );
  88. $plugins = views_fetch_plugin_data();
  89. // Register theme functions for all style plugins
  90. foreach ($plugins as $type => $info) {
  91. foreach ($info as $plugin => $def) {
  92. if (isset($def['theme']) && (!isset($def['register theme']) || !empty($def['register theme']))) {
  93. $hooks[$def['theme']] = array(
  94. 'pattern' => $def['theme'] . '__',
  95. 'file' => $def['theme file'],
  96. 'path' => $def['theme path'],
  97. 'variables' => $variables[$type],
  98. );
  99. $include = DRUPAL_ROOT . '/' . $def['theme path'] . '/' . $def['theme file'];
  100. if (file_exists($include)) {
  101. require_once $include;
  102. }
  103. if (!function_exists('theme_' . $def['theme'])) {
  104. $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
  105. }
  106. }
  107. if (isset($def['additional themes'])) {
  108. foreach ($def['additional themes'] as $theme => $theme_type) {
  109. if (empty($theme_type)) {
  110. $theme = $theme_type;
  111. $theme_type = $type;
  112. }
  113. $hooks[$theme] = array(
  114. 'pattern' => $theme . '__',
  115. 'file' => $def['theme file'],
  116. 'path' => $def['theme path'],
  117. 'variables' => $variables[$theme_type],
  118. );
  119. if (!function_exists('theme_' . $theme)) {
  120. $hooks[$theme]['template'] = drupal_clean_css_identifier($theme);
  121. }
  122. }
  123. }
  124. }
  125. }
  126. $hooks['views_form_views_form'] = $base + array(
  127. 'render element' => 'form',
  128. );
  129. $hooks['views_exposed_form'] = $base + array(
  130. 'template' => 'views-exposed-form',
  131. 'pattern' => 'views_exposed_form__',
  132. 'render element' => 'form',
  133. );
  134. $hooks['views_more'] = $base + array(
  135. 'template' => 'views-more',
  136. 'pattern' => 'views_more__',
  137. 'variables' => array('more_url' => NULL, 'link_text' => 'more', 'view' => NULL),
  138. );
  139. // Add theme suggestions which are part of modules.
  140. foreach (views_get_module_apis() as $info) {
  141. if (isset($info['template path'])) {
  142. $hooks += _views_find_module_templates($hooks, $info['template path']);
  143. }
  144. }
  145. return $hooks;
  146. }
  147. /**
  148. * Scans a directory of a module for template files.
  149. *
  150. * @param $cache
  151. * The existing cache of theme hooks to test against.
  152. * @param $path
  153. * The path to search.
  154. *
  155. * @see drupal_find_theme_templates()
  156. */
  157. function _views_find_module_templates($cache, $path) {
  158. $templates = array();
  159. $regex = '/' . '\.tpl\.php' . '$' . '/';
  160. // Because drupal_system_listing works the way it does, we check for real
  161. // templates separately from checking for patterns.
  162. $files = drupal_system_listing($regex, $path, 'name', 0);
  163. foreach ($files as $template => $file) {
  164. // Chop off the remaining extensions if there are any. $template already
  165. // has the rightmost extension removed, but there might still be more,
  166. // such as with .tpl.php, which still has .tpl in $template at this point.
  167. if (($pos = strpos($template, '.')) !== FALSE) {
  168. $template = substr($template, 0, $pos);
  169. }
  170. // Transform - in filenames to _ to match function naming scheme
  171. // for the purposes of searching.
  172. $hook = strtr($template, '-', '_');
  173. if (isset($cache[$hook])) {
  174. $templates[$hook] = array(
  175. 'template' => $template,
  176. 'path' => dirname($file->filename),
  177. 'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL,
  178. );
  179. }
  180. // Ensure that the pattern is maintained from base themes to its sub-themes.
  181. // Each sub-theme will have their templates scanned so the pattern must be
  182. // held for subsequent runs.
  183. if (isset($cache[$hook]['pattern'])) {
  184. $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
  185. }
  186. }
  187. $patterns = array_keys($files);
  188. foreach ($cache as $hook => $info) {
  189. if (!empty($info['pattern'])) {
  190. // Transform _ in pattern to - to match file naming scheme
  191. // for the purposes of searching.
  192. $pattern = strtr($info['pattern'], '_', '-');
  193. $matches = preg_grep('/^'. $pattern .'/', $patterns);
  194. if ($matches) {
  195. foreach ($matches as $match) {
  196. $file = substr($match, 0, strpos($match, '.'));
  197. // Put the underscores back in for the hook name and register this pattern.
  198. $templates[strtr($file, '-', '_')] = array(
  199. 'template' => $file,
  200. 'path' => dirname($files[$match]->uri),
  201. 'variables' => isset($info['variables']) ? $info['variables'] : NULL,
  202. 'render element' => isset($info['render element']) ? $info['render element'] : NULL,
  203. 'base hook' => $hook,
  204. 'includes' => isset($info['includes']) ? $info['includes'] : NULL,
  205. );
  206. }
  207. }
  208. }
  209. }
  210. return $templates;
  211. }
  212. /**
  213. * Returns a list of plugins and metadata about them.
  214. *
  215. * @return array
  216. * An array keyed by PLUGIN_TYPE:PLUGIN_NAME, like 'display:page' or
  217. * 'pager:full', containing an array with the following keys:
  218. * - title: The plugin's title.
  219. * - type: The plugin type.
  220. * - module: The module providing the plugin.
  221. * - views: An array of enabled Views that are currently using this plugin,
  222. * keyed by machine name.
  223. */
  224. function views_plugin_list() {
  225. $plugin_data = views_fetch_plugin_data();
  226. $plugins = array();
  227. foreach (views_get_enabled_views() as $view) {
  228. foreach ($view->display as $display_id => $display) {
  229. foreach ($plugin_data as $type => $info) {
  230. if ($type == 'display' && isset($display->display_plugin)) {
  231. $name = $display->display_plugin;
  232. }
  233. elseif (isset($display->display_options["{$type}_plugin"])) {
  234. $name = $display->display_options["{$type}_plugin"];
  235. }
  236. elseif (isset($display->display_options[$type]['type'])) {
  237. $name = $display->display_options[$type]['type'];
  238. }
  239. else {
  240. continue;
  241. }
  242. // Key first by the plugin type, then the name.
  243. $key = $type . ':' . $name;
  244. // Add info for this plugin.
  245. if (!isset($plugins[$key])) {
  246. $plugins[$key] = array(
  247. 'type' => $type,
  248. 'title' => check_plain($info[$name]['title']),
  249. 'module' => check_plain($info[$name]['module']),
  250. 'views' => array(),
  251. );
  252. }
  253. // Add this view to the list for this plugin.
  254. $plugins[$key]['views'][$view->name] = $view->name;
  255. }
  256. }
  257. }
  258. return $plugins;
  259. }
  260. /**
  261. * A theme preprocess function to automatically allow view-based node
  262. * templates if called from a view.
  263. *
  264. * The 'modules/node.views.inc' file is a better place for this, but
  265. * we haven't got a chance to load that file before Drupal builds the
  266. * node portion of the theme registry.
  267. */
  268. function views_preprocess_node(&$vars) {
  269. // The 'view' attribute of the node is added in views_preprocess_node()
  270. if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
  271. $vars['view'] = $vars['node']->view;
  272. $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name;
  273. if (!empty($vars['node']->view->current_display)) {
  274. $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
  275. // If a node is being rendered in a view, and the view does not have a path,
  276. // prevent drupal from accidentally setting the $page variable:
  277. if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
  278. $vars['page'] = FALSE;
  279. }
  280. }
  281. }
  282. // Allow to alter comments and links based on the settings in the row plugin.
  283. if (!empty($vars['view']->style_plugin->row_plugin) && get_class($vars['view']->style_plugin->row_plugin) == 'views_plugin_row_node_view') {
  284. node_row_node_view_preprocess_node($vars);
  285. }
  286. }
  287. /**
  288. * A theme preprocess function to automatically allow view-based node
  289. * templates if called from a view.
  290. */
  291. function views_preprocess_comment(&$vars) {
  292. // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
  293. if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
  294. $vars['view'] = &$vars['node']->view;
  295. $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name;
  296. if (!empty($vars['node']->view->current_display)) {
  297. $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
  298. }
  299. }
  300. }
  301. /**
  302. * Implement hook_permission().
  303. */
  304. function views_permission() {
  305. return array(
  306. 'administer views' => array(
  307. 'title' => t('Administer views'),
  308. 'description' => t('Access the views administration pages.'),
  309. 'restrict access' => TRUE,
  310. ),
  311. 'access all views' => array(
  312. 'title' => t('Bypass views access control'),
  313. 'description' => t('Bypass access control when accessing views.'),
  314. 'restrict access' => TRUE,
  315. ),
  316. );
  317. }
  318. /**
  319. * Implement hook_menu().
  320. */
  321. function views_menu() {
  322. $items = array();
  323. $items['views/ajax'] = array(
  324. 'title' => 'Views',
  325. 'page callback' => 'views_ajax',
  326. 'theme callback' => 'ajax_base_page_theme',
  327. 'delivery callback' => 'ajax_deliver',
  328. 'access callback' => TRUE,
  329. 'description' => 'Ajax callback for view loading.',
  330. 'type' => MENU_CALLBACK,
  331. 'file' => 'includes/ajax.inc',
  332. );
  333. // Path is not admin/structure/views due to menu complications with the wildcards from
  334. // the generic ajax callback.
  335. $items['admin/views/ajax/autocomplete/user'] = array(
  336. 'page callback' => 'views_ajax_autocomplete_user',
  337. 'theme callback' => 'ajax_base_page_theme',
  338. 'access callback' => 'user_access',
  339. 'access arguments' => array('access user profiles'),
  340. 'type' => MENU_CALLBACK,
  341. 'file' => 'includes/ajax.inc',
  342. );
  343. // Define another taxonomy autocomplete because the default one of drupal
  344. // does not support a vid a argument anymore
  345. $items['admin/views/ajax/autocomplete/taxonomy'] = array(
  346. 'page callback' => 'views_ajax_autocomplete_taxonomy',
  347. 'theme callback' => 'ajax_base_page_theme',
  348. 'access callback' => 'user_access',
  349. 'access arguments' => array('access content'),
  350. 'type' => MENU_CALLBACK,
  351. 'file' => 'includes/ajax.inc',
  352. );
  353. return $items;
  354. }
  355. /**
  356. * Implement hook_menu_alter().
  357. */
  358. function views_menu_alter(&$callbacks) {
  359. $our_paths = array();
  360. $views = views_get_applicable_views('uses hook menu');
  361. foreach ($views as $data) {
  362. list($view, $display_id) = $data;
  363. $result = $view->execute_hook_menu($display_id, $callbacks);
  364. if (is_array($result)) {
  365. // The menu system doesn't support having two otherwise
  366. // identical paths with different placeholders. So we
  367. // want to remove the existing items from the menu whose
  368. // paths would conflict with ours.
  369. // First, we must find any existing menu items that may
  370. // conflict. We use a regular expression because we don't
  371. // know what placeholders they might use. Note that we
  372. // first construct the regex itself by replacing %views_arg
  373. // in the display path, then we use this constructed regex
  374. // (which will be something like '#^(foo/%[^/]*/bar)$#') to
  375. // search through the existing paths.
  376. $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
  377. $matches = preg_grep($regex, array_keys($callbacks));
  378. // Remove any conflicting items that were found.
  379. foreach ($matches as $path) {
  380. // Don't remove the paths we just added!
  381. if (!isset($our_paths[$path])) {
  382. unset($callbacks[$path]);
  383. }
  384. }
  385. foreach ($result as $path => $item) {
  386. if (!isset($callbacks[$path])) {
  387. // Add a new item, possibly replacing (and thus effectively
  388. // overriding) one that we removed above.
  389. $callbacks[$path] = $item;
  390. }
  391. else {
  392. // This item already exists, so it must be one that we added.
  393. // We change the various callback arguments to pass an array
  394. // of possible display IDs instead of a single ID.
  395. $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
  396. $callbacks[$path]['page arguments'][1][] = $display_id;
  397. $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
  398. $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
  399. $callbacks[$path]['load arguments'][1][] = $display_id;
  400. }
  401. $our_paths[$path] = TRUE;
  402. }
  403. }
  404. }
  405. // Save memory: Destroy those views.
  406. foreach ($views as $data) {
  407. list($view, $display_id) = $data;
  408. $view->destroy();
  409. }
  410. }
  411. /**
  412. * Helper function for menu loading. This will automatically be
  413. * called in order to 'load' a views argument; primarily it
  414. * will be used to perform validation.
  415. *
  416. * @param $value
  417. * The actual value passed.
  418. * @param $name
  419. * The name of the view. This needs to be specified in the 'load function'
  420. * of the menu entry.
  421. * @param $display_id
  422. * The display id that will be loaded for this menu item.
  423. * @param $index
  424. * The menu argument index. This counts from 1.
  425. */
  426. function views_arg_load($value, $name, $display_id, $index) {
  427. static $views = array();
  428. $display_ids = is_array($display_id) ? $display_id : array($display_id);
  429. $display_id = reset($display_ids);
  430. foreach ($display_ids as $id) {
  431. // Make sure we haven't already loaded this views argument for a similar
  432. // menu item elsewhere. Since access is always checked for the current user,
  433. // we are sure that the static cache contains valid entries.
  434. $key = $name . ':' . $id . ':' . $value . ':' . $index;
  435. if (isset($views[$key])) {
  436. return $views[$key];
  437. }
  438. // Lazy load the view object to avoid unnecessary work.
  439. if (!isset($view)) {
  440. $view = views_get_view($name);
  441. }
  442. // Pick the first display we have access to.
  443. if ($view && count($display_ids) > 1 && $view->access($id)) {
  444. $display_id = $id;
  445. break;
  446. }
  447. }
  448. if ($view) {
  449. $view->set_display($display_id);
  450. $view->init_handlers();
  451. $ids = array_keys($view->argument);
  452. $indexes = array();
  453. $path = explode('/', $view->get_path());
  454. foreach ($path as $id => $piece) {
  455. if ($piece == '%' && !empty($ids)) {
  456. $indexes[$id] = array_shift($ids);
  457. }
  458. }
  459. if (isset($indexes[$index])) {
  460. if (isset($view->argument[$indexes[$index]])) {
  461. $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
  462. $view->destroy();
  463. // Store the output in case we load this same menu item again.
  464. $views[$key] = $arg;
  465. return $arg;
  466. }
  467. }
  468. $view->destroy();
  469. }
  470. }
  471. /**
  472. * Page callback: Displays a page view, given a name and display id.
  473. *
  474. * @param $name
  475. * The name of a view.
  476. * @param $display_id
  477. * The display id of a view.
  478. *
  479. * @return
  480. * Either the HTML of a fully-executed view, or MENU_NOT_FOUND.
  481. */
  482. function views_page($name, $display_id) {
  483. $args = func_get_args();
  484. // Remove $name and $display_id from the arguments.
  485. array_shift($args);
  486. array_shift($args);
  487. // Load the view and render it.
  488. if ($view = views_get_view($name)) {
  489. return $view->execute_display($display_id, $args);
  490. }
  491. // Fallback; if we get here no view was found or handler was not valid.
  492. return MENU_NOT_FOUND;
  493. }
  494. /**
  495. * Implements hook_page_alter().
  496. */
  497. function views_page_alter(&$page) {
  498. // If the main content of this page contains a view, attach its contextual
  499. // links to the overall page array. This allows them to be rendered directly
  500. // next to the page title.
  501. $view = views_get_page_view();
  502. if (!empty($view)) {
  503. // If a module is still putting in the display like we used to, catch that.
  504. if (is_subclass_of($view, 'views_plugin_display')) {
  505. $view = $view->view;
  506. }
  507. views_add_contextual_links($page, 'page', $view, $view->current_display);
  508. }
  509. }
  510. /**
  511. * Implements MODULE_preprocess_HOOK() for html.tpl.php.
  512. */
  513. function views_preprocess_html(&$variables) {
  514. // If the page contains a view as its main content, contextual links may have
  515. // been attached to the page as a whole; for example, by views_page_alter().
  516. // This allows them to be associated with the page and rendered by default
  517. // next to the page title (which we want). However, it also causes the
  518. // Contextual Links module to treat the wrapper for the entire page (i.e.,
  519. // the <body> tag) as the HTML element that these contextual links are
  520. // associated with. This we don't want; for better visual highlighting, we
  521. // prefer a smaller region to be chosen. The region we prefer differs from
  522. // theme to theme and depends on the details of the theme's markup in
  523. // page.tpl.php, so we can only find it using JavaScript. We therefore remove
  524. // the "contextual-links-region" class from the <body> tag here and add
  525. // JavaScript that will insert it back in the correct place.
  526. if (!empty($variables['page']['#views_contextual_links_info'])) {
  527. $key = array_search('contextual-links-region', $variables['classes_array']);
  528. if ($key !== FALSE) {
  529. $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
  530. // Add the JavaScript, with a group and weight such that it will run
  531. // before modules/contextual/contextual.js.
  532. drupal_add_js(drupal_get_path('module', 'views') . '/js/views-contextual.js', array('group' => JS_LIBRARY, 'weight' => -1));
  533. }
  534. }
  535. }
  536. /**
  537. * Implements hook_preprocess_HOOK() for page.tpl.php.
  538. */
  539. function views_preprocess_page(&$variables) {
  540. // If the page contains a view as its main content, contextual links may have
  541. // been attached to the page as a whole; for example, by views_page_alter().
  542. // This allows them to be associated with the page and rendered by default
  543. // next to the page title (which we want). However, it also causes the
  544. // Contextual Links module to treat the wrapper for the entire page (i.e.,
  545. // the <body> tag) as the HTML element that these contextual links are
  546. // associated with. This we don't want; for better visual highlighting, we
  547. // prefer a smaller region to be chosen. The region we prefer differs from
  548. // theme to theme and depends on the details of the theme's markup in
  549. // page.tpl.php, so we can only find it using JavaScript. We therefore remove
  550. // the "contextual-links-region" class from the <body> tag here and add
  551. // JavaScript that will insert it back in the correct place.
  552. if (!empty($variables['page']['#views_contextual_links_info'])) {
  553. $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
  554. }
  555. }
  556. /**
  557. * Implements hook_contextual_links_view_alter().
  558. */
  559. function views_contextual_links_view_alter(&$element, $items) {
  560. // If we are rendering views-related contextual links attached to the overall
  561. // page array, add a class to the list of contextual links. This will be used
  562. // by the JavaScript added in views_preprocess_html().
  563. if (!empty($element['#element']['#views_contextual_links_info']) && !empty($element['#element']['#type']) && $element['#element']['#type'] == 'page') {
  564. $element['#attributes']['class'][] = 'views-contextual-links-page';
  565. }
  566. }
  567. /**
  568. * Implement hook_block_info().
  569. */
  570. function views_block_info() {
  571. // Try to avoid instantiating all the views just to get the blocks info.
  572. views_include('cache');
  573. $cache = views_cache_get('views_block_items', TRUE);
  574. if ($cache && is_array($cache->data)) {
  575. return $cache->data;
  576. }
  577. $items = array();
  578. $views = views_get_all_views();
  579. foreach ($views as $view) {
  580. // disabled views get nothing.
  581. if (!empty($view->disabled)) {
  582. continue;
  583. }
  584. $view->init_display();
  585. foreach ($view->display as $display_id => $display) {
  586. if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
  587. $result = $display->handler->execute_hook_block_list();
  588. if (is_array($result)) {
  589. $items = array_merge($items, $result);
  590. }
  591. }
  592. if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
  593. $result = $display->handler->get_special_blocks();
  594. if (is_array($result)) {
  595. $items = array_merge($items, $result);
  596. }
  597. }
  598. }
  599. }
  600. // block.module has a delta length limit of 32, but our deltas can
  601. // unfortunately be longer because view names can be 32 and display IDs
  602. // can also be 32. So for very long deltas, change to md5 hashes.
  603. $hashes = array();
  604. // get the keys because we're modifying the array and we don't want to
  605. // confuse PHP too much.
  606. $keys = array_keys($items);
  607. foreach ($keys as $delta) {
  608. if (strlen($delta) >= 32) {
  609. $hash = md5($delta);
  610. $hashes[$hash] = $delta;
  611. $items[$hash] = $items[$delta];
  612. unset($items[$delta]);
  613. }
  614. }
  615. // Only save hashes if they have changed.
  616. $old_hashes = variable_get('views_block_hashes', array());
  617. if ($hashes != $old_hashes) {
  618. variable_set('views_block_hashes', $hashes);
  619. }
  620. // Save memory: Destroy those views.
  621. foreach ($views as $view) {
  622. $view->destroy();
  623. }
  624. views_cache_set('views_block_items', $items, TRUE);
  625. return $items;
  626. }
  627. /**
  628. * Implement hook_block_view().
  629. */
  630. function views_block_view($delta) {
  631. $start = microtime(TRUE);
  632. // if this is 32, this should be an md5 hash.
  633. if (strlen($delta) == 32) {
  634. $hashes = variable_get('views_block_hashes', array());
  635. if (!empty($hashes[$delta])) {
  636. $delta = $hashes[$delta];
  637. }
  638. }
  639. // This indicates it's a special one.
  640. if (substr($delta, 0, 1) == '-') {
  641. list($nothing, $type, $name, $display_id) = explode('-', $delta);
  642. // Put the - back on.
  643. $type = '-' . $type;
  644. if ($view = views_get_view($name)) {
  645. if ($view->access($display_id)) {
  646. $view->set_display($display_id);
  647. if (isset($view->display_handler)) {
  648. $output = $view->display_handler->view_special_blocks($type);
  649. // Before returning the block output, convert it to a renderable
  650. // array with contextual links.
  651. views_add_block_contextual_links($output, $view, $display_id, 'special_block_' . $type);
  652. $view->destroy();
  653. return $output;
  654. }
  655. }
  656. $view->destroy();
  657. }
  658. }
  659. // If the delta doesn't contain valid data return nothing.
  660. $explode = explode('-', $delta);
  661. if (count($explode) != 2) {
  662. return;
  663. }
  664. list($name, $display_id) = $explode;
  665. // Load the view
  666. if ($view = views_get_view($name)) {
  667. if ($view->access($display_id)) {
  668. $output = $view->execute_display($display_id);
  669. // Before returning the block output, convert it to a renderable array
  670. // with contextual links.
  671. views_add_block_contextual_links($output, $view, $display_id);
  672. $view->destroy();
  673. return $output;
  674. }
  675. $view->destroy();
  676. }
  677. }
  678. /**
  679. * Converts Views block content to a renderable array with contextual links.
  680. *
  681. * @param $block
  682. * An array representing the block, with the same structure as the return
  683. * value of hook_block_view(). This will be modified so as to force
  684. * $block['content'] to be a renderable array, containing the optional
  685. * '#contextual_links' property (if there are any contextual links associated
  686. * with the block).
  687. * @param $view
  688. * The view that was used to generate the block content.
  689. * @param $display_id
  690. * The ID of the display within the view that was used to generate the block
  691. * content.
  692. * @param $block_type
  693. * The type of the block. If it's block it's a regular views display,
  694. * but 'special_block_-exp' exist as well.
  695. */
  696. function views_add_block_contextual_links(&$block, $view, $display_id, $block_type = 'block') {
  697. // Do not add contextual links to an empty block.
  698. if (!empty($block['content'])) {
  699. // Contextual links only work on blocks whose content is a renderable
  700. // array, so if the block contains a string of already-rendered markup,
  701. // convert it to an array.
  702. if (is_string($block['content'])) {
  703. $block['content'] = array('#markup' => $block['content']);
  704. }
  705. // Add the contextual links.
  706. views_add_contextual_links($block['content'], $block_type, $view, $display_id);
  707. }
  708. }
  709. /**
  710. * Adds contextual links associated with a view display to a renderable array.
  711. *
  712. * This function should be called when a view is being rendered in a particular
  713. * location and you want to attach the appropriate contextual links (e.g.,
  714. * links for editing the view) to it.
  715. *
  716. * The function operates by checking the view's display plugin to see if it has
  717. * defined any contextual links that are intended to be displayed in the
  718. * requested location; if so, it attaches them. The contextual links intended
  719. * for a particular location are defined by the 'contextual links' and
  720. * 'contextual links locations' properties in hook_views_plugins() and
  721. * hook_views_plugins_alter(); as a result, these hook implementations have
  722. * full control over where and how contextual links are rendered for each
  723. * display.
  724. *
  725. * In addition to attaching the contextual links to the passed-in array (via
  726. * the standard #contextual_links property), this function also attaches
  727. * additional information via the #views_contextual_links_info property. This
  728. * stores an array whose keys are the names of each module that provided
  729. * views-related contextual links (same as the keys of the #contextual_links
  730. * array itself) and whose values are themselves arrays whose keys ('location',
  731. * 'view_name', and 'view_display_id') store the location, name of the view,
  732. * and display ID that were passed in to this function. This allows you to
  733. * access information about the contextual links and how they were generated in
  734. * a variety of contexts where you might be manipulating the renderable array
  735. * later on (for example, alter hooks which run later during the same page
  736. * request).
  737. *
  738. * @param $render_element
  739. * The renderable array to which contextual links will be added. This array
  740. * should be suitable for passing in to drupal_render() and will normally
  741. * contain a representation of the view display whose contextual links are
  742. * being requested.
  743. * @param $location
  744. * The location in which the calling function intends to render the view and
  745. * its contextual links. The core system supports three options for this
  746. * parameter:
  747. * - 'block': Used when rendering a block which contains a view. This
  748. * retrieves any contextual links intended to be attached to the block
  749. * itself.
  750. * - 'page': Used when rendering the main content of a page which contains a
  751. * view. This retrieves any contextual links intended to be attached to the
  752. * page itself (for example, links which are displayed directly next to the
  753. * page title).
  754. * - 'view': Used when rendering the view itself, in any context. This
  755. * retrieves any contextual links intended to be attached directly to the
  756. * view.
  757. * If you are rendering a view and its contextual links in another location,
  758. * you can pass in a different value for this parameter. However, you will
  759. * also need to use hook_views_plugins() or hook_views_plugins_alter() to
  760. * declare, via the 'contextual links locations' array key, which view
  761. * displays support having their contextual links rendered in the location
  762. * you have defined.
  763. * @param $view
  764. * The view whose contextual links will be added.
  765. * @param $display_id
  766. * The ID of the display within $view whose contextual links will be added.
  767. *
  768. * @see hook_views_plugins()
  769. * @see views_block_view()
  770. * @see views_page_alter()
  771. * @see template_preprocess_views_view()
  772. */
  773. function views_add_contextual_links(&$render_element, $location, $view, $display_id) {
  774. // Do not do anything if the view is configured to hide its administrative
  775. // links.
  776. if (empty($view->hide_admin_links)) {
  777. // Also do not do anything if the display plugin has not defined any
  778. // contextual links that are intended to be displayed in the requested
  779. // location.
  780. $plugin = views_fetch_plugin_data('display', $view->display[$display_id]->display_plugin);
  781. // If contextual links locations are not set, provide a sane default. (To
  782. // avoid displaying any contextual links at all, a display plugin can still
  783. // set 'contextual links locations' to, e.g., an empty array.)
  784. $plugin += array('contextual links locations' => array('view'));
  785. // On exposed_forms blocks contextual links should always be visible.
  786. $plugin['contextual links locations'][] = 'special_block_-exp';
  787. $has_links = !empty($plugin['contextual links']) && !empty($plugin['contextual links locations']);
  788. if ($has_links && in_array($location, $plugin['contextual links locations'])) {
  789. foreach ($plugin['contextual links'] as $module => $link) {
  790. $args = array();
  791. $valid = TRUE;
  792. if (!empty($link['argument properties'])) {
  793. foreach ($link['argument properties'] as $property) {
  794. // If the plugin is trying to create an invalid contextual link
  795. // (for example, "path/to/{$view->property}", where $view->property
  796. // does not exist), we cannot construct the link, so we skip it.
  797. if (!property_exists($view, $property)) {
  798. $valid = FALSE;
  799. break;
  800. }
  801. else {
  802. $args[] = $view->{$property};
  803. }
  804. }
  805. }
  806. // If the link was valid, attach information about it to the renderable
  807. // array.
  808. if ($valid) {
  809. $render_element['#contextual_links'][$module] = array($link['parent path'], $args);
  810. $render_element['#views_contextual_links_info'][$module] = array(
  811. 'location' => $location,
  812. 'view' => $view,
  813. 'view_name' => $view->name,
  814. 'view_display_id' => $display_id,
  815. );
  816. }
  817. }
  818. }
  819. }
  820. }
  821. /**
  822. * Returns an array of language names.
  823. *
  824. * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
  825. *
  826. * @param $field
  827. * 'name' => names in current language, localized
  828. * 'native' => native names
  829. * @param $all
  830. * Boolean to return all languages or only enabled ones
  831. *
  832. * @see locale_language_list()
  833. */
  834. function views_language_list($field = 'name', $all = FALSE) {
  835. if ($all) {
  836. $languages = language_list();
  837. }
  838. else {
  839. $languages = language_list('enabled');
  840. $languages = $languages[1];
  841. }
  842. $list = array();
  843. foreach ($languages as $language) {
  844. $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
  845. }
  846. return $list;
  847. }
  848. /**
  849. * Implements hook_flush_caches().
  850. */
  851. function views_flush_caches() {
  852. return array('cache_views', 'cache_views_data');
  853. }
  854. /**
  855. * Implements hook_field_create_instance.
  856. */
  857. function views_field_create_instance($instance) {
  858. cache_clear_all('*', 'cache_views', TRUE);
  859. cache_clear_all('*', 'cache_views_data', TRUE);
  860. }
  861. /**
  862. * Implements hook_field_update_instance.
  863. */
  864. function views_field_update_instance($instance, $prior_instance) {
  865. cache_clear_all('*', 'cache_views', TRUE);
  866. cache_clear_all('*', 'cache_views_data', TRUE);
  867. }
  868. /**
  869. * Implements hook_field_delete_instance.
  870. */
  871. function views_field_delete_instance($instance) {
  872. cache_clear_all('*', 'cache_views', TRUE);
  873. cache_clear_all('*', 'cache_views_data', TRUE);
  874. }
  875. /**
  876. * Invalidate the views cache, forcing a rebuild on the next grab of table data.
  877. */
  878. function views_invalidate_cache() {
  879. // Clear the views cache.
  880. cache_clear_all('*', 'cache_views', TRUE);
  881. // Clear the page and block cache.
  882. cache_clear_all();
  883. // Set the menu as needed to be rebuilt.
  884. variable_set('menu_rebuild_needed', TRUE);
  885. // Allow modules to respond to the Views cache being cleared.
  886. module_invoke_all('views_invalidate_cache');
  887. }
  888. /**
  889. * Access callback to determine if the user can import Views.
  890. *
  891. * View imports require an additional access check because they are PHP
  892. * code and PHP is more locked down than administer views.
  893. */
  894. function views_import_access() {
  895. return user_access('administer views') && user_access('use PHP for settings');
  896. }
  897. /**
  898. * Determine if the logged in user has access to a view.
  899. *
  900. * This function should only be called from a menu hook or some other
  901. * embedded source. Each argument is the result of a call to
  902. * views_plugin_access::get_access_callback() which is then used
  903. * to determine if that display is accessible. If *any* argument
  904. * is accessible, then the view is accessible.
  905. */
  906. function views_access() {
  907. $args = func_get_args();
  908. foreach ($args as $arg) {
  909. if ($arg === TRUE) {
  910. return TRUE;
  911. }
  912. if (!is_array($arg)) {
  913. continue;
  914. }
  915. list($callback, $arguments) = $arg;
  916. $arguments = $arguments ? $arguments : array();
  917. // Bring dynamic arguments to the access callback.
  918. foreach ($arguments as $key => $value) {
  919. if (is_int($value) && isset($args[$value])) {
  920. $arguments[$key] = $args[$value];
  921. }
  922. }
  923. if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
  924. return TRUE;
  925. }
  926. }
  927. return FALSE;
  928. }
  929. /**
  930. * Access callback for the views_plugin_access_perm access plugin.
  931. *
  932. * Determine if the specified user has access to a view on the basis of
  933. * permissions. If the $account argument is omitted, the current user
  934. * is used.
  935. */
  936. function views_check_perm($perms, $account = NULL) {
  937. // Backward compatibility to ensure also a single permission string is
  938. // properly processed.
  939. $perms = is_array($perms) ? $perms : array($perms);
  940. if (user_access('access all views', $account)) {
  941. return TRUE;
  942. }
  943. // Perms are handled as OR, as soon one permission allows access TRUE is
  944. // returned.
  945. foreach ($perms as $perm) {
  946. if (user_access($perm, $account)) {
  947. return TRUE;
  948. }
  949. }
  950. return FALSE;
  951. }
  952. /**
  953. * Access callback for the views_plugin_access_role access plugin.
  954. * Determine if the specified user has access to a view on the basis of any of
  955. * the requested roles. If the $account argument is omitted, the current user
  956. * is used.
  957. */
  958. function views_check_roles($rids, $account = NULL) {
  959. global $user;
  960. $account = isset($account) ? $account : $user;
  961. $roles = array_keys($account->roles);
  962. $roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
  963. return user_access('access all views', $account) || array_intersect(array_filter($rids), $roles);
  964. }
  965. // ------------------------------------------------------------------
  966. // Functions to help identify views that are running or ran
  967. /**
  968. * Set the current 'page view' that is being displayed so that it is easy
  969. * for other modules or the theme to identify.
  970. */
  971. function &views_set_page_view($view = NULL) {
  972. static $cache = NULL;
  973. if (isset($view)) {
  974. $cache = $view;
  975. }
  976. return $cache;
  977. }
  978. /**
  979. * Find out what, if any, page view is currently in use. Please note that
  980. * this returns a reference, so be careful! You can unintentionally modify the
  981. * $view object.
  982. *
  983. * @return view
  984. * A fully formed, empty $view object.
  985. */
  986. function &views_get_page_view() {
  987. return views_set_page_view();
  988. }
  989. /**
  990. * Set the current 'current view' that is being built/rendered so that it is
  991. * easy for other modules or items in drupal_eval to identify
  992. *
  993. * @return view
  994. */
  995. function &views_set_current_view($view = NULL) {
  996. static $cache = NULL;
  997. if (isset($view)) {
  998. $cache = $view;
  999. }
  1000. return $cache;
  1001. }
  1002. /**
  1003. * Find out what, if any, current view is currently in use. Please note that
  1004. * this returns a reference, so be careful! You can unintentionally modify the
  1005. * $view object.
  1006. *
  1007. * @return view
  1008. */
  1009. function &views_get_current_view() {
  1010. return views_set_current_view();
  1011. }
  1012. // ------------------------------------------------------------------
  1013. // Include file helpers
  1014. /**
  1015. * Include views .inc files as necessary.
  1016. */
  1017. function views_include($file) {
  1018. ctools_include($file, 'views');
  1019. }
  1020. /**
  1021. * Load views files on behalf of modules.
  1022. */
  1023. function views_module_include($api, $reset = FALSE) {
  1024. if ($reset) {
  1025. $cache = &drupal_static('ctools_plugin_api_info');
  1026. if (isset($cache['views']['views'])) {
  1027. unset($cache['views']['views']);
  1028. }
  1029. }
  1030. ctools_include('plugins');
  1031. return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version());
  1032. }
  1033. /**
  1034. * Get a list of modules that support the current views API.
  1035. */
  1036. function views_get_module_apis($api = 'views', $reset = FALSE) {
  1037. if ($reset) {
  1038. $cache = &drupal_static('ctools_plugin_api_info');
  1039. if (isset($cache['views']['views'])) {
  1040. unset($cache['views']['views']);
  1041. }
  1042. }
  1043. ctools_include('plugins');
  1044. return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version());
  1045. }
  1046. /**
  1047. * Include views .css files.
  1048. */
  1049. function views_add_css($file) {
  1050. // We set preprocess to FALSE because we are adding the files conditionally,
  1051. // and we don't want to generate duplicate cache files.
  1052. // TODO: at some point investigate adding some files unconditionally and
  1053. // allowing preprocess.
  1054. drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE));
  1055. }
  1056. /**
  1057. * Include views .js files.
  1058. */
  1059. function views_add_js($file) {
  1060. // If javascript has been disabled by the user, never add js files.
  1061. if (variable_get('views_no_javascript', FALSE)) {
  1062. return;
  1063. }
  1064. static $base = TRUE, $ajax = TRUE;
  1065. if ($base) {
  1066. drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
  1067. $base = FALSE;
  1068. }
  1069. if ($ajax && in_array($file, array('ajax', 'ajax_view'))) {
  1070. drupal_add_library('system', 'drupal.ajax');
  1071. drupal_add_library('system', 'jquery.form');
  1072. $ajax = FALSE;
  1073. }
  1074. ctools_add_js($file, 'views');
  1075. }
  1076. /**
  1077. * Load views files on behalf of modules.
  1078. */
  1079. function views_include_handlers($reset = FALSE) {
  1080. static $finished = FALSE;
  1081. // Ensure this only gets run once.
  1082. if ($finished && !$reset) {
  1083. return;
  1084. }
  1085. views_include('base');
  1086. views_include('handlers');
  1087. views_include('cache');
  1088. views_include('plugins');
  1089. views_module_include('views', $reset);
  1090. $finished = TRUE;
  1091. }
  1092. // -----------------------------------------------------------------------
  1093. // Views handler functions
  1094. /**
  1095. * Fetch a handler from the data cache.
  1096. *
  1097. * @param $table
  1098. * The name of the table this handler is from.
  1099. * @param $field
  1100. * The name of the field this handler is from.
  1101. * @param $key
  1102. * The type of handler. i.e, sort, field, argument, filter, relationship
  1103. * @param $override
  1104. * Override the actual handler object with this class. Used for
  1105. * aggregation when the handler is redirected to the aggregation
  1106. * handler.
  1107. *
  1108. * @return views_handler
  1109. * An instance of a handler object. May be views_handler_broken.
  1110. */
  1111. function views_get_handler($table, $field, $key, $override = NULL) {
  1112. static $recursion_protection = array();
  1113. $data = views_fetch_data($table, FALSE);
  1114. $handler = NULL;
  1115. views_include('handlers');
  1116. // Support old views_data entries conversion.
  1117. // Support conversion on table level.
  1118. if (isset($data['moved to'])) {
  1119. $moved = array($data['moved to'], $field);
  1120. }
  1121. // Support conversion on datafield level.
  1122. if (isset($data[$field]['moved to'])) {
  1123. $moved = $data[$field]['moved to'];
  1124. }
  1125. // Support conversion on handler level.
  1126. if (isset($data[$field][$key]['moved to'])) {
  1127. $moved = $data[$field][$key]['moved to'];
  1128. }
  1129. if (isset($data[$field][$key]) || !empty($moved)) {
  1130. if (!empty($moved)) {
  1131. list($moved_table, $moved_field) = $moved;
  1132. if (!empty($recursion_protection[$moved_table][$moved_field])) {
  1133. // recursion detected!
  1134. return NULL;
  1135. }
  1136. $recursion_protection[$moved_table][$moved_field] = TRUE;
  1137. $handler = views_get_handler($moved_table, $moved_field, $key, $override);
  1138. $recursion_protection = array();
  1139. if ($handler) {
  1140. // store these values so we know what we were originally called.
  1141. $handler->original_table = $table;
  1142. $handler->original_field = $field;
  1143. if (empty($handler->actual_table)) {
  1144. $handler->actual_table = $moved_table;
  1145. $handler->actual_field = $moved_field;
  1146. }
  1147. }
  1148. return $handler;
  1149. }
  1150. // Set up a default handler:
  1151. if (empty($data[$field][$key]['handler'])) {
  1152. $data[$field][$key]['handler'] = 'views_handler_' . $key;
  1153. }
  1154. if ($override) {
  1155. $data[$field][$key]['override handler'] = $override;
  1156. }
  1157. $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
  1158. }
  1159. if ($handler) {
  1160. return $handler;
  1161. }
  1162. // DEBUG -- identify missing handlers
  1163. vpr("Missing handler: @table @field @key", array('@table' => $table, '@field' => $field, '@key' => $key));
  1164. $broken = array(
  1165. 'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
  1166. 'handler' => 'views_handler_' . $key . '_broken',
  1167. 'table' => $table,
  1168. 'field' => $field,
  1169. );
  1170. return _views_create_handler($broken, 'handler', $key);
  1171. }
  1172. /**
  1173. * Fetch Views' data from the cache
  1174. */
  1175. function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
  1176. views_include('cache');
  1177. return _views_fetch_data($table, $move, $reset);
  1178. }
  1179. // -----------------------------------------------------------------------
  1180. // Views plugin functions
  1181. /**
  1182. * Fetch the plugin data from cache.
  1183. */
  1184. function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
  1185. views_include('cache');
  1186. return _views_fetch_plugin_data($type, $plugin, $reset);
  1187. }
  1188. /**
  1189. * Fetch a list of all base tables available
  1190. *
  1191. * @param $type
  1192. * Either 'display', 'style' or 'row'
  1193. * @param $key
  1194. * For style plugins, this is an optional type to restrict to. May be 'normal',
  1195. * 'summary', 'feed' or others based on the needs of the display.
  1196. * @param $base
  1197. * An array of possible base tables.
  1198. *
  1199. * @return
  1200. * A keyed array of in the form of 'base_table' => 'Description'.
  1201. */
  1202. function views_fetch_plugin_names($type, $key = NULL, $base = array()) {
  1203. $data = views_fetch_plugin_data();
  1204. $plugins[$type] = array();
  1205. foreach ($data[$type] as $id => $plugin) {
  1206. // Skip plugins that don't conform to our key.
  1207. if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
  1208. continue;
  1209. }
  1210. if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) {
  1211. $plugins[$type][$id] = $plugin['title'];
  1212. }
  1213. }
  1214. if (!empty($plugins[$type])) {
  1215. asort($plugins[$type]);
  1216. return $plugins[$type];
  1217. }
  1218. // fall-through
  1219. return array();
  1220. }
  1221. /**
  1222. * Get a handler for a plugin
  1223. *
  1224. * @return views_plugin
  1225. *
  1226. * The created plugin object.
  1227. */
  1228. function views_get_plugin($type, $plugin, $reset = FALSE) {
  1229. views_include('handlers');
  1230. $definition = views_fetch_plugin_data($type, $plugin, $reset);
  1231. if (!empty($definition)) {
  1232. return _views_create_handler($definition, $type);
  1233. }
  1234. }
  1235. /**
  1236. * Load the current enabled localization plugin.
  1237. *
  1238. * @return The name of the localization plugin.
  1239. */
  1240. function views_get_localization_plugin() {
  1241. $plugin = variable_get('views_localization_plugin', '');
  1242. // Provide sane default values for the localization plugin.
  1243. if (empty($plugin)) {
  1244. if (module_exists('locale')) {
  1245. $plugin = 'core';
  1246. }
  1247. else {
  1248. $plugin = 'none';
  1249. }
  1250. }
  1251. return $plugin;
  1252. }
  1253. // -----------------------------------------------------------------------
  1254. // Views database functions
  1255. /**
  1256. * Get all view templates.
  1257. *
  1258. * Templates are special in-code views that are never active, but exist only
  1259. * to be cloned into real views as though they were templates.
  1260. */
  1261. function views_get_all_templates() {
  1262. $templates = array();
  1263. $modules = views_module_include('views_template');
  1264. foreach ($modules as $module => $info) {
  1265. $function = $module . '_views_templates';
  1266. if (function_exists($function)) {
  1267. $new = $function();
  1268. if ($new && is_array($new)) {
  1269. $templates = array_merge($new, $templates);
  1270. }
  1271. }
  1272. }
  1273. return $templates;
  1274. }
  1275. /**
  1276. * Create an empty view to work with.
  1277. *
  1278. * @return view
  1279. * A fully formed, empty $view object. This object must be populated before
  1280. * it can be successfully saved.
  1281. */
  1282. function views_new_view() {
  1283. views_include('view');
  1284. $view = new view();
  1285. $view->vid = 'new';
  1286. $view->add_display('default');
  1287. return $view;
  1288. }
  1289. /**
  1290. * Return a list of all views and display IDs that have a particular
  1291. * setting in their display's plugin settings.
  1292. *
  1293. * @return
  1294. * @code
  1295. * array(
  1296. * array($view, $display_id),
  1297. * array($view, $display_id),
  1298. * );
  1299. * @endcode
  1300. */
  1301. function views_get_applicable_views($type) {
  1302. // @todo: Use a smarter flagging system so that we don't have to
  1303. // load every view for this.
  1304. $result = array();
  1305. $views = views_get_all_views();
  1306. foreach ($views as $view) {
  1307. // Skip disabled views.
  1308. if (!empty($view->disabled)) {
  1309. continue;
  1310. }
  1311. if (empty($view->display)) {
  1312. // Skip this view as it is broken.
  1313. vsm(t("Skipping broken view @view", array('@view' => $view->name)));
  1314. continue;
  1315. }
  1316. // Loop on array keys because something seems to muck with $view->display
  1317. // a bit in PHP4.
  1318. foreach (array_keys($view->display) as $id) {
  1319. $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
  1320. if (!empty($plugin[$type])) {
  1321. // This view uses hook menu. Clone it so that different handlers
  1322. // don't trip over each other, and add it to the list.
  1323. $v = $view->clone_view();
  1324. if ($v->set_display($id) && $v->display_handler->get_option('enabled')) {
  1325. $result[] = array($v, $id);
  1326. }
  1327. // In PHP 4.4.7 and presumably earlier, if we do not unset $v
  1328. // here, we will find that it actually overwrites references
  1329. // possibly due to shallow copying issues.
  1330. unset($v);
  1331. }
  1332. }
  1333. }
  1334. return $result;
  1335. }
  1336. /**
  1337. * Return an array of all views as fully loaded $view objects.
  1338. *
  1339. * @param $reset
  1340. * If TRUE, reset the static cache forcing views to be reloaded.
  1341. */
  1342. function views_get_all_views($reset = FALSE) {
  1343. ctools_include('export');
  1344. return ctools_export_crud_load_all('views_view', $reset);
  1345. }
  1346. /**
  1347. * Returns an array of all enabled views, as fully loaded $view objects.
  1348. */
  1349. function views_get_enabled_views() {
  1350. $views = views_get_all_views();
  1351. return array_filter($views, 'views_view_is_enabled');
  1352. }
  1353. /**
  1354. * Returns an array of all disabled views, as fully loaded $view objects.
  1355. */
  1356. function views_get_disabled_views() {
  1357. $views = views_get_all_views();
  1358. return array_filter($views, 'views_view_is_disabled');
  1359. }
  1360. /**
  1361. * Return an array of view as options array, that can be used by select,
  1362. * checkboxes and radios as #options.
  1363. *
  1364. * @param bool $views_only
  1365. * If TRUE, only return views, not displays.
  1366. * @param string $filter
  1367. * Filters the views on status. Can either be 'all' (default), 'enabled' or
  1368. * 'disabled'
  1369. * @param mixed $exclude_view
  1370. * view or current display to exclude
  1371. * either a
  1372. * - views object (containing $exclude_view->name and $exclude_view->current_display)
  1373. * - views name as string: e.g. my_view
  1374. * - views name and display id (separated by ':'): e.g. my_view:default
  1375. * @param bool $optgroup
  1376. * If TRUE, returns an array with optgroups for each view (will be ignored for
  1377. * $views_only = TRUE). Can be used by select
  1378. * @param bool $sort
  1379. * If TRUE, the list of views is sorted ascending.
  1380. *
  1381. * @return array
  1382. * an associative array for use in select.
  1383. * - key: view name and display id separated by ':', or the view name only
  1384. */
  1385. function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE, $sort = FALSE) {
  1386. // Filter the big views array.
  1387. switch ($filter) {
  1388. case 'all':
  1389. case 'disabled':
  1390. case 'enabled':
  1391. $func = "views_get_{$filter}_views";
  1392. $views = $func();
  1393. break;
  1394. default:
  1395. return array();
  1396. }
  1397. // Prepare exclude view strings for comparison.
  1398. if (empty($exclude_view)) {
  1399. $exclude_view_name = '';
  1400. $exclude_view_display = '';
  1401. }
  1402. elseif (is_object($exclude_view)) {
  1403. $exclude_view_name = $exclude_view->name;
  1404. $exclude_view_display = $exclude_view->current_display;
  1405. }
  1406. else {
  1407. list($exclude_view_name, $exclude_view_display) = explode(':', $exclude_view);
  1408. }
  1409. $options = array();
  1410. foreach ($views as $view) {
  1411. // Return only views.
  1412. if ($views_only && $view->name != $exclude_view_name) {
  1413. $options[$view->name] = $view->get_human_name();
  1414. }
  1415. // Return views with display ids.
  1416. else {
  1417. foreach ($view->display as $display_id => $display) {
  1418. if (!($view->name == $exclude_view_name && $display_id == $exclude_view_display)) {
  1419. if ($optgroup) {
  1420. $options[$view->name][$view->name . ':' . $display->id] = t('@view : @display', array('@view' => $view->name, '@display' => $display->id));
  1421. }
  1422. else {
  1423. $options[$view->name . ':' . $display->id] = t('View: @view - Display: @display', array('@view' => $view->name, '@display' => $display->id));
  1424. }
  1425. }
  1426. }
  1427. }
  1428. }
  1429. if ($sort) {
  1430. ksort($options);
  1431. }
  1432. return $options;
  1433. }
  1434. /**
  1435. * Returns TRUE if a view is enabled, FALSE otherwise.
  1436. */
  1437. function views_view_is_enabled($view) {
  1438. return empty($view->disabled);
  1439. }
  1440. /**
  1441. * Returns TRUE if a view is disabled, FALSE otherwise.
  1442. */
  1443. function views_view_is_disabled($view) {
  1444. return !empty($view->disabled);
  1445. }
  1446. /**
  1447. * Get a view from the database or from default views.
  1448. *
  1449. * This function is just a static wrapper around views::load(). This function
  1450. * isn't called 'views_load()' primarily because it might get a view
  1451. * from the default views which aren't technically loaded from the database.
  1452. *
  1453. * @param $name
  1454. * The name of the view.
  1455. * @param $reset
  1456. * If TRUE, reset this entry in the load cache.
  1457. * @return view
  1458. * A reference to the $view object. Use $reset if you're sure you want
  1459. * a fresh one.
  1460. */
  1461. function views_get_view($name, $reset = FALSE) {
  1462. if ($reset) {
  1463. $cache = &drupal_static('ctools_export_load_object');
  1464. if (isset($cache['views_view'][$name])) {
  1465. unset($cache['views_view'][$name]);
  1466. }
  1467. }
  1468. ctools_include('export');
  1469. $view = ctools_export_crud_load('views_view', $name);
  1470. if ($view) {
  1471. $view->update();
  1472. return $view->clone_view();
  1473. }
  1474. }
  1475. /**
  1476. * Find the real location of a table.
  1477. *
  1478. * If a table has moved, find the new name of the table so that we can
  1479. * change its name directly in options where necessary.
  1480. */
  1481. function views_move_table($table) {
  1482. $data = views_fetch_data($table, FALSE);
  1483. if (isset($data['moved to'])) {
  1484. $table = $data['moved to'];
  1485. }
  1486. return $table;
  1487. }
  1488. /**
  1489. * Export callback to load the view subrecords, which are the displays.
  1490. */
  1491. function views_load_display_records(&$views) {
  1492. // Get vids from the views.
  1493. $names = array();
  1494. foreach ($views as $view) {
  1495. if (empty($view->display)) {
  1496. $names[$view->vid] = $view->name;
  1497. }
  1498. }
  1499. if (empty($names)) {
  1500. return;
  1501. }
  1502. foreach (view::db_objects() as $key) {
  1503. $object_name = "views_$key";
  1504. $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
  1505. array(':vids' => array_keys($names)));
  1506. foreach ($result as $data) {
  1507. $object = new $object_name(FALSE);
  1508. $object->load_row($data);
  1509. // Because it can get complicated with this much indirection,
  1510. // make a shortcut reference.
  1511. $location = &$views[$names[$object->vid]]->$key;
  1512. // If we have a basic id field, load the item onto the view based on
  1513. // this ID, otherwise push it on.
  1514. if (!empty($object->id)) {
  1515. $location[$object->id] = $object;
  1516. }
  1517. else {
  1518. $location[] = $object;
  1519. }
  1520. }
  1521. }
  1522. }
  1523. /**
  1524. * Export CRUD callback to save a view.
  1525. */
  1526. function views_save_view(&$view) {
  1527. return $view->save();
  1528. }
  1529. /**
  1530. * Export CRUD callback to delete a view.
  1531. */
  1532. function views_delete_view(&$view) {
  1533. return $view->delete(TRUE);
  1534. }
  1535. /**
  1536. * Export CRUD callback to export a view.
  1537. */
  1538. function views_export_view(&$view, $indent = '') {
  1539. return $view->export($indent);
  1540. }
  1541. /**
  1542. * Export callback to change view status.
  1543. */
  1544. function views_export_status($view, $status) {
  1545. ctools_export_set_object_status($view, $status);
  1546. views_invalidate_cache();
  1547. }
  1548. // ------------------------------------------------------------------
  1549. // Views debug helper functions
  1550. /**
  1551. * Provide debug output for Views.
  1552. *
  1553. * This relies on devel.module
  1554. * or on the debug() function if you use a simpletest.
  1555. *
  1556. * @param $message
  1557. * The message/variable which should be debugged.
  1558. * This either could be
  1559. * * an array/object which is converted to pretty output
  1560. * * a translation source string which is used together with the parameter placeholders.
  1561. *
  1562. * @param $placeholder
  1563. * The placeholders which are used for the translation source string.
  1564. */
  1565. function views_debug($message, $placeholders = array()) {
  1566. if (!is_string($message)) {
  1567. $output = '<pre>' . var_export($message, TRUE) . '</pre>';
  1568. }
  1569. if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
  1570. $devel_region = variable_get('views_devel_region', 'footer');
  1571. if ($devel_region == 'watchdog') {
  1572. $output = $message;
  1573. watchdog('views_logging', $output, $placeholders);
  1574. }
  1575. else if ($devel_region == 'drupal_debug') {
  1576. $output = empty($output) ? t($message, $placeholders) : $output;
  1577. dd($output);
  1578. }
  1579. else {
  1580. $output = empty($output) ? t($message, $placeholders) : $output;
  1581. dpm($output);
  1582. }
  1583. }
  1584. elseif (isset($GLOBALS['drupal_test_info'])) {
  1585. $output = empty($output) ? t($message, $placeholders) : $output;
  1586. debug($output);
  1587. }
  1588. }
  1589. /**
  1590. * Shortcut to views_debug()
  1591. */
  1592. function vpr($message, $placeholders = array()) {
  1593. views_debug($message, $placeholders);
  1594. }
  1595. /**
  1596. * Debug messages
  1597. */
  1598. function vsm($message) {
  1599. if (module_exists('devel')) {
  1600. dpm($message);
  1601. }
  1602. }
  1603. function views_trace() {
  1604. $message = '';
  1605. foreach (debug_backtrace() as $item) {
  1606. if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
  1607. $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
  1608. }
  1609. }
  1610. return $message;
  1611. }
  1612. function vsm_trace() {
  1613. vsm(views_trace());
  1614. }
  1615. function vpr_trace() {
  1616. dpr(views_trace());
  1617. }
  1618. // ------------------------------------------------------------------
  1619. // Views form (View with form elements)
  1620. /**
  1621. * Returns TRUE if the passed-in view contains handlers with views form
  1622. * implementations, FALSE otherwise.
  1623. */
  1624. function views_view_has_form_elements($view) {
  1625. foreach ($view->field as $field) {
  1626. if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
  1627. return TRUE;
  1628. }
  1629. }
  1630. $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
  1631. $empty = empty($view->result);
  1632. foreach ($area_handlers as $area) {
  1633. if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
  1634. return TRUE;
  1635. }
  1636. }
  1637. return FALSE;
  1638. }
  1639. /**
  1640. * This is the entry function. Just gets the form for the current step.
  1641. * The form is always assumed to be multistep, even if it has only one
  1642. * step (the default 'views_form_views_form' step). That way it is actually
  1643. * possible for modules to have a multistep form if they need to.
  1644. */
  1645. function views_form($form, &$form_state, $view, $output) {
  1646. $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';
  1647. // Cache the built form to prevent it from being rebuilt prior to validation
  1648. // and submission, which could lead to data being processed incorrectly,
  1649. // because the views rows (and thus, the form elements as well) have changed
  1650. // in the meantime.
  1651. $form_state['cache'] = TRUE;
  1652. $form = array();
  1653. $query = drupal_get_query_parameters($_GET, array('q'));
  1654. $form['#action'] = url($view->get_url(), array('query' => $query));
  1655. // Tell the preprocessor whether it should hide the header, footer, pager...
  1656. $form['show_view_elements'] = array(
  1657. '#type' => 'value',
  1658. '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
  1659. );
  1660. $form = $form_state['step']($form, $form_state, $view, $output);
  1661. return $form;
  1662. }
  1663. /**
  1664. * Callback for the main step of a Views form.
  1665. * Invoked by views_form().
  1666. */
  1667. function views_form_views_form($form, &$form_state, $view, $output) {
  1668. $form['#prefix'] = '<div class="views-form">';
  1669. $form['#suffix'] = '</div>';
  1670. $form['#theme'] = 'views_form_views_form';
  1671. $form['#validate'][] = 'views_form_views_form_validate';
  1672. $form['#submit'][] = 'views_form_views_form_submit';
  1673. // Add the output markup to the form array so that it's included when the form
  1674. // array is passed to the theme function.
  1675. $form['output'] = array(
  1676. '#type' => 'markup',
  1677. '#markup' => $output,
  1678. // This way any additional form elements will go before the view
  1679. // (below the exposed widgets).
  1680. '#weight' => 50,
  1681. );
  1682. $substitutions = array();
  1683. foreach ($view->field as $field_name => $field) {
  1684. $form_element_name = $field_name;
  1685. if (method_exists($field, 'form_element_name')) {
  1686. $form_element_name = $field->form_element_name();
  1687. }
  1688. $method_form_element_row_id_exists = FALSE;
  1689. if (method_exists($field, 'form_element_row_id')) {
  1690. $method_form_element_row_id_exists = TRUE;
  1691. }
  1692. // If the field provides a views form, allow it to modify the $form array.
  1693. $has_form = FALSE;
  1694. if (property_exists($field, 'views_form_callback')) {
  1695. $callback = $field->views_form_callback;
  1696. $callback($view, $field, $form, $form_state);
  1697. $has_form = TRUE;
  1698. }
  1699. elseif (method_exists($field, 'views_form')) {
  1700. $field->views_form($form, $form_state);
  1701. $has_form = TRUE;
  1702. }
  1703. // Build the substitutions array for use in the theme function.
  1704. if ($has_form) {
  1705. foreach ($view->result as $row_id => $row) {
  1706. if ($method_form_element_row_id_exists) {
  1707. $form_element_row_id = $field->form_element_row_id($row_id);
  1708. }
  1709. else {
  1710. $form_element_row_id = $row_id;
  1711. }
  1712. $substitutions[] = array(
  1713. 'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
  1714. 'field_name' => $form_element_name,
  1715. 'row_id' => $form_element_row_id,
  1716. );
  1717. }
  1718. }
  1719. }
  1720. // Give the area handlers a chance to extend the form.
  1721. $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
  1722. $empty = empty($view->result);
  1723. foreach ($area_handlers as $area) {
  1724. if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
  1725. $area->views_form($form, $form_state);
  1726. }
  1727. }
  1728. $form['#substitutions'] = array(
  1729. '#type' => 'value',
  1730. '#value' => $substitutions,
  1731. );
  1732. $form['actions'] = array(
  1733. '#type' => 'container',
  1734. '#attributes' => array('class' => array('form-actions')),
  1735. '#weight' => 100,
  1736. );
  1737. $form['actions']['submit'] = array(
  1738. '#type' => 'submit',
  1739. '#value' => t('Save'),
  1740. );
  1741. return $form;
  1742. }
  1743. /**
  1744. * Validate handler for the first step of the views form.
  1745. * Calls any existing views_form_validate functions located
  1746. * on the views fields.
  1747. */
  1748. function views_form_views_form_validate($form, &$form_state) {
  1749. $view = $form_state['build_info']['args'][0];
  1750. // Call the validation method on every field handler that has it.
  1751. foreach ($view->field as $field_name => $field) {
  1752. if (method_exists($field, 'views_form_validate')) {
  1753. $field->views_form_validate($form, $form_state);
  1754. }
  1755. }
  1756. // Call the validate method on every area handler that has it.
  1757. foreach (array('header', 'footer') as $area) {
  1758. foreach ($view->{$area} as $area_name => $area_handler) {
  1759. if (method_exists($area_handler, 'views_form_validate')) {
  1760. $area_handler->views_form_validate($form, $form_state);
  1761. }
  1762. }
  1763. }
  1764. }
  1765. /**
  1766. * Submit handler for the first step of the views form.
  1767. * Calls any existing views_form_submit functions located
  1768. * on the views fields.
  1769. */
  1770. function views_form_views_form_submit($form, &$form_state) {
  1771. $view = $form_state['build_info']['args'][0];
  1772. // Call the submit method on every field handler that has it.
  1773. foreach ($view->field as $field_name => $field) {
  1774. if (method_exists($field, 'views_form_submit')) {
  1775. $field->views_form_submit($form, $form_state);
  1776. }
  1777. }
  1778. // Call the submit method on every area handler that has it.
  1779. foreach (array('header', 'footer') as $area) {
  1780. foreach ($view->{$area} as $area_name => $area_handler) {
  1781. if (method_exists($area_handler, 'views_form_submit')) {
  1782. $area_handler->views_form_submit($form, $form_state);
  1783. }
  1784. }
  1785. }
  1786. }
  1787. // ------------------------------------------------------------------
  1788. // Exposed widgets form
  1789. /**
  1790. * Form builder for the exposed widgets form.
  1791. *
  1792. * Be sure that $view and $display are references.
  1793. */
  1794. function views_exposed_form($form, &$form_state) {
  1795. // Don't show the form when batch operations are in progress.
  1796. if ($batch = batch_get() && isset($batch['current_set'])) {
  1797. return array(
  1798. // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
  1799. '#theme' => '',
  1800. );
  1801. }
  1802. // Make sure that we validate because this form might be submitted
  1803. // multiple times per page.
  1804. $form_state['must_validate'] = TRUE;
  1805. $view = &$form_state['view'];
  1806. $display = &$form_state['display'];
  1807. $form_state['input'] = $view->get_exposed_input();
  1808. // Let form plugins know this is for exposed widgets.
  1809. $form_state['exposed'] = TRUE;
  1810. // Check if the form was already created
  1811. if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
  1812. return $cache;
  1813. }
  1814. $form['#info'] = array();
  1815. if (!variable_get('clean_url', FALSE)) {
  1816. $form['q'] = array(
  1817. '#type' => 'hidden',
  1818. '#value' => $view->get_url(),
  1819. );
  1820. }
  1821. // Go through each handler and let it generate its exposed widget.
  1822. foreach ($view->display_handler->handlers as $type => $value) {
  1823. foreach ($view->$type as $id => $handler) {
  1824. if ($handler->can_expose() && $handler->is_exposed()) {
  1825. // Grouped exposed filters have their own forms.
  1826. // Instead of render the standard exposed form, a new Select or
  1827. // Radio form field is rendered with the available groups.
  1828. // When an user choose an option the selected value is split
  1829. // into the operator and value that the item represents.
  1830. if ($handler->is_a_group()) {
  1831. $handler->group_form($form, $form_state);
  1832. $id = $handler->options['group_info']['identifier'];
  1833. }
  1834. else {
  1835. $handler->exposed_form($form, $form_state);
  1836. }
  1837. if ($info = $handler->exposed_info()) {
  1838. $form['#info']["$type-$id"] = $info;
  1839. }
  1840. }
  1841. }
  1842. }
  1843. $form['submit'] = array(
  1844. '#name' => '', // prevent from showing up in $_GET.
  1845. '#type' => 'submit',
  1846. '#value' => t('Apply'),
  1847. '#id' => drupal_html_id('edit-submit-' . $view->name),
  1848. );
  1849. $form['#action'] = url($view->display_handler->get_url());
  1850. $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
  1851. $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
  1852. // $form['#attributes']['class'] = array('views-exposed-form');
  1853. // If using AJAX, we need the form plugin.
  1854. if ($view->use_ajax) {
  1855. drupal_add_library('system', 'jquery.form');
  1856. }
  1857. ctools_include('dependent');
  1858. $exposed_form_plugin = $form_state['exposed_form_plugin'];
  1859. $exposed_form_plugin->exposed_form_alter($form, $form_state);
  1860. // Save the form
  1861. views_exposed_form_cache($view->name, $view->current_display, $form);
  1862. return $form;
  1863. }
  1864. /**
  1865. * Implement hook_form_alter for the exposed form.
  1866. *
  1867. * Since the exposed form is a GET form, we don't want it to send a wide
  1868. * variety of information.
  1869. */
  1870. function views_form_views_exposed_form_alter(&$form, &$form_state) {
  1871. $form['form_build_id']['#access'] = FALSE;
  1872. $form['form_token']['#access'] = FALSE;
  1873. $form['form_id']['#access'] = FALSE;
  1874. }
  1875. /**
  1876. * Validate handler for exposed filters
  1877. */
  1878. function views_exposed_form_validate(&$form, &$form_state) {
  1879. foreach (array('field', 'filter') as $type) {
  1880. $handlers = &$form_state['view']->$type;
  1881. foreach ($handlers as $key => $handler) {
  1882. $handlers[$key]->exposed_validate($form, $form_state);
  1883. }
  1884. }
  1885. $exposed_form_plugin = $form_state['exposed_form_plugin'];
  1886. $exposed_form_plugin->exposed_form_validate($form, $form_state);
  1887. }
  1888. /**
  1889. * Submit handler for exposed filters
  1890. */
  1891. function views_exposed_form_submit(&$form, &$form_state) {
  1892. foreach (array('field', 'filter') as $type) {
  1893. $handlers = &$form_state['view']->$type;
  1894. foreach ($handlers as $key => $info) {
  1895. $handlers[$key]->exposed_submit($form, $form_state);
  1896. }
  1897. }
  1898. $form_state['view']->exposed_data = $form_state['values'];
  1899. $form_state['view']->exposed_raw_input = array();
  1900. $exclude = array('q', 'submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
  1901. $exposed_form_plugin = $form_state['exposed_form_plugin'];
  1902. $exposed_form_plugin->exposed_form_submit($form, $form_state, $exclude);
  1903. foreach ($form_state['values'] as $key => $value) {
  1904. if (!in_array($key, $exclude)) {
  1905. $form_state['view']->exposed_raw_input[$key] = $value;
  1906. }
  1907. }
  1908. }
  1909. /**
  1910. * Save the Views exposed form for later use.
  1911. *
  1912. * @param $views_name
  1913. * String. The views name.
  1914. * @param $display_name
  1915. * String. The current view display name.
  1916. * @param $form_output
  1917. * Array (optional). The form structure. Only needed when inserting the value.
  1918. * @return
  1919. * Array. The form structure, if any. Otherwise, return FALSE.
  1920. */
  1921. function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
  1922. // When running tests for exposed filters, this cache should
  1923. // be cleared between each test.
  1924. $views_exposed = &drupal_static(__FUNCTION__);
  1925. // Save the form output
  1926. if (!empty($form_output)) {
  1927. $views_exposed[$views_name][$display_name] = $form_output;
  1928. return;
  1929. }
  1930. // Return the form output, if any
  1931. return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
  1932. }
  1933. // ------------------------------------------------------------------
  1934. // Misc helpers
  1935. /**
  1936. * Build a list of theme function names for use most everywhere.
  1937. */
  1938. function views_theme_functions($hook, $view, $display = NULL) {
  1939. require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'views') . "/theme/theme.inc";
  1940. return _views_theme_functions($hook, $view, $display);
  1941. }
  1942. /**
  1943. * Substitute current time; this works with cached queries.
  1944. */
  1945. function views_views_query_substitutions($view) {
  1946. global $language_content;
  1947. return array(
  1948. '***CURRENT_VERSION***' => VERSION,
  1949. '***CURRENT_TIME***' => REQUEST_TIME,
  1950. '***CURRENT_LANGUAGE***' => $language_content->language,
  1951. '***DEFAULT_LANGUAGE***' => language_default('language'),
  1952. );
  1953. }
  1954. /**
  1955. * Implements hook_query_TAG_alter().
  1956. *
  1957. * This is the hook_query_alter() for queries tagged by Views and is used to
  1958. * add in substitutions from hook_views_query_substitutions().
  1959. */
  1960. function views_query_views_alter(QueryAlterableInterface $query) {
  1961. $substitutions = $query->getMetaData('views_substitutions');
  1962. $tables =& $query->getTables();
  1963. $where =& $query->conditions();
  1964. // Replaces substitions in tables.
  1965. foreach ($tables as $table_name => $table_metadata) {
  1966. foreach ($table_metadata['arguments'] as $replacement_key => $value) {
  1967. if (isset($substitutions[$value])) {
  1968. $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
  1969. }
  1970. }
  1971. }
  1972. // Replaces substitions in filter criterias.
  1973. _views_query_tag_alter_condition($query, $where, $substitutions);
  1974. }
  1975. /**
  1976. * Replaces the substitutions recursive foreach condition.
  1977. */
  1978. function _views_query_tag_alter_condition(QueryAlterableInterface $query, &$conditions, $substitutions) {
  1979. foreach ($conditions as $condition_id => &$condition) {
  1980. if (is_numeric($condition_id)) {
  1981. if (is_string($condition['field'])) {
  1982. $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
  1983. }
  1984. elseif (is_object($condition['field'])) {
  1985. $sub_conditions =& $condition['field']->conditions();
  1986. _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
  1987. }
  1988. // $condition['value'] is a subquery so alter the subquery recursive.
  1989. // Therefore take sure to get the metadata of the main query.
  1990. if (is_object($condition['value'])) {
  1991. $subquery = $condition['value'];
  1992. $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
  1993. views_query_views_alter($condition['value']);
  1994. }
  1995. elseif (isset($condition['value'])) {
  1996. $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
  1997. }
  1998. }
  1999. }
  2000. }
  2001. /**
  2002. * Embed a view using a PHP snippet.
  2003. *
  2004. * This function is meant to be called from PHP snippets, should one wish to
  2005. * embed a view in a node or something. It's meant to provide the simplest
  2006. * solution and doesn't really offer a lot of options, but breaking the function
  2007. * apart is pretty easy, and this provides a worthwhile guide to doing so.
  2008. *
  2009. * Note that this function does NOT display the title of the view. If you want
  2010. * to do that, you will need to do what this function does manually, by
  2011. * loading the view, getting the preview and then getting $view->get_title().
  2012. *
  2013. * @param $name
  2014. * The name of the view to embed.
  2015. * @param $display_id
  2016. * The display id to embed. If unsure, use 'default', as it will always be
  2017. * valid. But things like 'page' or 'block' should work here.
  2018. * @param ...
  2019. * Any additional parameters will be passed as arguments.
  2020. */
  2021. function views_embed_view($name, $display_id = 'default') {
  2022. $args = func_get_args();
  2023. array_shift($args); // remove $name
  2024. if (count($args)) {
  2025. array_shift($args); // remove $display_id
  2026. }
  2027. $view = views_get_view($name);
  2028. if (!$view || !$view->access($display_id)) {
  2029. return;
  2030. }
  2031. return $view->preview($display_id, $args);
  2032. }
  2033. /**
  2034. * Get the result of a view.
  2035. *
  2036. * @param string $name
  2037. * The name of the view to retrieve the data from.
  2038. * @param string $display_id
  2039. * The display id. On the edit page for the view in question, you'll find
  2040. * a list of displays at the left side of the control area. "Master"
  2041. * will be at the top of that list. Hover your cursor over the name of the
  2042. * display you want to use. An URL will appear in the status bar of your
  2043. * browser. This is usually at the bottom of the window, in the chrome.
  2044. * Everything after #views-tab- is the display ID, e.g. page_1.
  2045. * @param ...
  2046. * Any additional parameters will be passed as arguments.
  2047. * @return array
  2048. * An array containing an object for each view item.
  2049. */
  2050. function views_get_view_result($name, $display_id = NULL) {
  2051. $args = func_get_args();
  2052. array_shift($args); // remove $name
  2053. if (count($args)) {
  2054. array_shift($args); // remove $display_id
  2055. }
  2056. $view = views_get_view($name);
  2057. if (is_object($view)) {
  2058. if (is_array($args)) {
  2059. $view->set_arguments($args);
  2060. }
  2061. if (is_string($display_id)) {
  2062. $view->set_display($display_id);
  2063. }
  2064. else {
  2065. $view->init_display();
  2066. }
  2067. $view->pre_execute();
  2068. $view->execute();
  2069. return $view->result;
  2070. }
  2071. else {
  2072. return array();
  2073. }
  2074. }
  2075. /**
  2076. * Export a field.
  2077. */
  2078. function views_var_export($var, $prefix = '', $init = TRUE) {
  2079. if (is_array($var)) {
  2080. if (empty($var)) {
  2081. $output = 'array()';
  2082. }
  2083. else {
  2084. $output = "array(\n";
  2085. foreach ($var as $key => $value) {
  2086. $output .= " " . views_var_export($key, '', FALSE) . " => " . views_var_export($value, ' ', FALSE) . ",\n";
  2087. }
  2088. $output .= ')';
  2089. }
  2090. }
  2091. elseif (is_bool($var)) {
  2092. $output = $var ? 'TRUE' : 'FALSE';
  2093. }
  2094. elseif (is_string($var) && strpos($var, "\n") !== FALSE) {
  2095. // Replace line breaks in strings with a token for replacement
  2096. // at the very end. This protects multi-line strings from
  2097. // unintentional indentation.
  2098. $var = str_replace("\n", "***BREAK***", $var);
  2099. $output = var_export($var, TRUE);
  2100. }
  2101. else {
  2102. $output = var_export($var, TRUE);
  2103. }
  2104. if ($prefix) {
  2105. $output = str_replace("\n", "\n$prefix", $output);
  2106. }
  2107. if ($init) {
  2108. $output = str_replace("***BREAK***", "\n", $output);
  2109. }
  2110. return $output;
  2111. }
  2112. /**
  2113. * Prepare a string for use as a valid CSS identifier (element, class or ID name).
  2114. * This function is similar to a core version but with more sane filter values.
  2115. *
  2116. * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
  2117. * CSS identifiers (including element names, classes, and IDs in selectors.)
  2118. *
  2119. * @param $identifier
  2120. * The identifier to clean.
  2121. * @param $filter
  2122. * An array of string replacements to use on the identifier.
  2123. * @return
  2124. * The cleaned identifier.
  2125. *
  2126. * @see drupal_clean_css_identifier()
  2127. */
  2128. function views_clean_css_identifier($identifier, $filter = array(' ' => '-', '/' => '-', '[' => '-', ']' => '')) {
  2129. // By default, we filter using Drupal's coding standards.
  2130. $identifier = strtr($identifier, $filter);
  2131. // Valid characters in a CSS identifier are:
  2132. // - the hyphen (U+002D)
  2133. // - a-z (U+0030 - U+0039)
  2134. // - A-Z (U+0041 - U+005A)
  2135. // - the underscore (U+005F)
  2136. // - 0-9 (U+0061 - U+007A)
  2137. // - ISO 10646 characters U+00A1 and higher
  2138. // We strip out any character not in the above list.
  2139. $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);
  2140. return $identifier;
  2141. }
  2142. /**
  2143. * Implement hook_views_exportables().
  2144. */
  2145. function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
  2146. $all_views = views_get_all_views();
  2147. if ($op == 'list') {
  2148. foreach ($all_views as $name => $view) {
  2149. // in list, $views is a list of tags.
  2150. if (empty($views) || in_array($view->tag, $views)) {
  2151. $return[$name] = array(
  2152. 'name' => check_plain($name),
  2153. 'desc' => check_plain($view->description),
  2154. 'tag' => check_plain($view->tag)
  2155. );
  2156. }
  2157. }
  2158. return $return;
  2159. }
  2160. if ($op == 'export') {
  2161. $code = "/**\n";
  2162. $code .= " * Implement hook_views_default_views().\n";
  2163. $code .= " */\n";
  2164. $code .= "function " . $name . "_views_default_views() {\n";
  2165. foreach ($views as $view => $truth) {
  2166. $code .= " /*\n";
  2167. $code .= " * View " . var_export($all_views[$view]->name, TRUE) . "\n";
  2168. $code .= " */\n";
  2169. $code .= $all_views[$view]->export(' ');
  2170. $code .= ' $views[$view->name] = $view;' . "\n\n";
  2171. }
  2172. $code .= " return \$views;\n";
  2173. $code .= "}\n";
  2174. return $code;
  2175. }
  2176. }
  2177. /**
  2178. * #process callback to see if we need to check_plain() the options.
  2179. *
  2180. * Since FAPI is inconsistent, the #options are sanitized for you in all cases
  2181. * _except_ checkboxes. We have form elements that are sometimes 'select' and
  2182. * sometimes 'checkboxes', so we need decide late in the form rendering cycle
  2183. * if the options need to be sanitized before they're rendered. This callback
  2184. * inspects the type, and if it's still 'checkboxes', does the sanitation.
  2185. */
  2186. function views_process_check_options($element, &$form_state) {
  2187. if ($element['#type'] == 'checkboxes' || $element['#type'] == 'checkbox') {
  2188. $element['#options'] = array_map('check_plain', $element['#options']);
  2189. }
  2190. return $element;
  2191. }
  2192. /**
  2193. * Trim the field down to the specified length.
  2194. *
  2195. * @param $alter
  2196. * - max_length: Maximum length of the string, the rest gets truncated.
  2197. * - word_boundary: Trim only on a word boundary.
  2198. * - ellipsis: Show an ellipsis (...) at the end of the trimmed string.
  2199. * - html: Take sure that the html is correct.
  2200. *
  2201. * @param $value
  2202. * The string which should be trimmed.
  2203. */
  2204. function views_trim_text($alter, $value) {
  2205. if (drupal_strlen($value) > $alter['max_length']) {
  2206. $value = drupal_substr($value, 0, $alter['max_length']);
  2207. // TODO: replace this with cleanstring of ctools
  2208. if (!empty($alter['word_boundary'])) {
  2209. $regex = "(.*)\b.+";
  2210. if (function_exists('mb_ereg')) {
  2211. mb_regex_encoding('UTF-8');
  2212. $found = mb_ereg($regex, $value, $matches);
  2213. }
  2214. else {
  2215. $found = preg_match("/$regex/us", $value, $matches);
  2216. }
  2217. if ($found) {
  2218. $value = $matches[1];
  2219. }
  2220. }
  2221. // Remove scraps of HTML entities from the end of a strings
  2222. $value = rtrim(preg_replace('/(?:<(?!.+>)|&(?!.+;)).*$/us', '', $value));
  2223. if (!empty($alter['ellipsis'])) {
  2224. $value .= t('...');
  2225. }
  2226. }
  2227. if (!empty($alter['html'])) {
  2228. $value = _filter_htmlcorrector($value);
  2229. }
  2230. return $value;
  2231. }
  2232. /**
  2233. * Adds one to each key of the array.
  2234. *
  2235. * For example array(0 => 'foo') would be array(1 => 'foo').
  2236. */
  2237. function views_array_key_plus($array) {
  2238. $keys = array_keys($array);
  2239. rsort($keys);
  2240. foreach ($keys as $key) {
  2241. $array[$key+1] = $array[$key];
  2242. unset($array[$key]);
  2243. }
  2244. asort($array);
  2245. return $array;
  2246. }
  2247. /**
  2248. * Report to CTools that we use hook_views_api instead of hook_ctools_plugin_api()
  2249. */
  2250. function views_ctools_plugin_api_hook_name() {
  2251. return 'views_api';
  2252. }
  2253. // Declare API compatibility on behalf of core modules:
  2254. /**
  2255. * Implements hook_views_api().
  2256. *
  2257. * This one is used as the base to reduce errors when updating.
  2258. */
  2259. function views_views_api() {
  2260. return array(
  2261. // in your modules do *not* use views_api_version()!!!
  2262. 'api' => views_api_version(),
  2263. 'path' => drupal_get_path('module', 'views') . '/modules',
  2264. );
  2265. }
  2266. if (!function_exists('aggregator_views_api')) {
  2267. function aggregator_views_api() { return views_views_api(); }
  2268. }
  2269. if (!function_exists('book_views_api')) {
  2270. function book_views_api() { return views_views_api(); }
  2271. }
  2272. if (!function_exists('comment_views_api')) {
  2273. function comment_views_api() { return views_views_api(); }
  2274. }
  2275. if (!function_exists('field_views_api')) {
  2276. function field_views_api() { return views_views_api(); }
  2277. }
  2278. if (!function_exists('file_views_api')) {
  2279. function file_views_api() { return views_views_api(); }
  2280. }
  2281. if (!function_exists('filter_views_api')) {
  2282. function filter_views_api() { return views_views_api(); }
  2283. }
  2284. if (!function_exists('image_views_api')) {
  2285. function image_views_api() { return views_views_api(); }
  2286. }
  2287. if (!function_exists('locale_views_api')) {
  2288. function locale_views_api() { return views_views_api(); }
  2289. }
  2290. if (!function_exists('node_views_api')) {
  2291. function node_views_api() { return views_views_api(); }
  2292. }
  2293. if (!function_exists('poll_views_api')) {
  2294. function poll_views_api() { return views_views_api(); }
  2295. }
  2296. if (!function_exists('profile_views_api')) {
  2297. function profile_views_api() { return views_views_api(); }
  2298. }
  2299. if (!function_exists('search_views_api')) {
  2300. function search_views_api() { return views_views_api(); }
  2301. }
  2302. if (!function_exists('statistics_views_api')) {
  2303. function statistics_views_api() { return views_views_api(); }
  2304. }
  2305. if (!function_exists('system_views_api')) {
  2306. function system_views_api() { return views_views_api(); }
  2307. }
  2308. if (!function_exists('tracker_views_api')) {
  2309. function tracker_views_api() { return views_views_api(); }
  2310. }
  2311. if (!function_exists('taxonomy_views_api')) {
  2312. function taxonomy_views_api() { return views_views_api(); }
  2313. }
  2314. if (!function_exists('translation_views_api')) {
  2315. function translation_views_api() { return views_views_api(); }
  2316. }
  2317. if (!function_exists('user_views_api')) {
  2318. function user_views_api() { return views_views_api(); }
  2319. }
  2320. if (!function_exists('contact_views_api')) {
  2321. function contact_views_api() { return views_views_api(); }
  2322. }