views.module 79 KB

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