views.module 79 KB

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