views.module 81 KB

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