Projet

Général

Profil

Paste
Télécharger (85,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / views / views.module @ 5d12d676

1
<?php
2

    
3
/**
4
 * @file
5
 * Primarily Drupal hooks and global API functions to manipulate views.
6
 *
7
 * This is the main module file for Views. The main entry points into
8
 * this module are views_page() and views_block(), where it handles
9
 * incoming page and block requests.
10
 */
11

    
12
/**
13
 * Implements hook_help().
14
 */
15
function views_help($path, $arg) {
16
  switch ($path) {
17
    case 'admin/help#views':
18
      $output = '';
19
      $output .= '<h3>' . t('About') . '</h3>';
20
      $output .= '<p>' . t('The Views module provides a back end to fetch information from content, user accounts, taxonomy terms, and other entities from the database and present it to the user as a grid, HTML list, table, unformatted list, etc. The resulting displays are known generally as views.') . '</p>';
21
      $output .= '<p>' . t('For more information, see the <a href="@views" target="blank">online documentation for the Views</a>.', array('@views' => 'https://www.drupal.org/documentation/modules/views')) . '</p>';
22
      $output .= '<p>' . t('In order to create and modify your own views using the administration and configuration user interface, you will need to enable either the Views UI module in core or a contributed module that provides a user interface for Views. See the <a href="/admin/structure/views">Views UI module help page</a> for more information.') . '</p>';
23

    
24
      $output .= '<h3>' . t('Uses') . '</h3>';
25
      $output .= '<dl>';
26
      $output .= '<dt>' . t('Adding functionality to administrative pages') . '</dt>';
27
      $output .= '<dd>' . t('The Views module adds functionality to some core administration pages. For example, <em>admin/content</em> uses Views to filter and sort content. With Views uninstalled, <em>admin/content</em> is more limited.') . '</dd>';
28

    
29
      $output .= '<dt>' . t('Expanding Views functionality') . '</dt>';
30
      $output .= '<dd>' . t('Contributed projects that support the Views module can be found in the  <a href="@views-related" target="blank">online documentation for Views-related contributed modules.</a>.', array('@views-related' => 'https://www.drupal.org/documentation/modules/views/add-ons')) . '</dd>';
31

    
32
      $output .= '<dt>' . t('Improving table accessibility') . '</dt>';
33
      $output .= '<dd>' . t('Views tables include semantic markup to improve accessibility. Data cells are automatically associated with header cells through id and header attributes. To improve the accessibility of your tables you can add descriptive elements within the Views table settings. The caption element can introduce context for a table, making it easier to understand. The summary element can provide an overview of how the data has been organized and how to navigate the table. Both the caption and summary are visible by default and also implemented according to HTML5 guidelines.') . '</dd>';
34
      return $output;
35
  }
36
}
37

    
38
/**
39
 * Advertise the current views api version.
40
 */
41
function views_api_version() {
42
  return '3.0';
43
}
44

    
45
/**
46
 * Implements hook_forms().
47
 *
48
 * To provide distinct form IDs for Views forms, the View name and
49
 * specific display name are appended to the base ID,
50
 * views_form_views_form. When such a form is built or submitted, this
51
 * function will return the proper callback function to use for the given form.
52
 */
53
function views_forms($form_id, $args) {
54
  if (strpos($form_id, 'views_form_') === 0) {
55
    return array(
56
      $form_id => array(
57
        'callback' => 'views_form',
58
      ),
59
    );
60
  }
61
}
62

    
63
/**
64
 * Returns a form ID for a Views form using the name and display of the View.
65
 */
66
function views_form_id($view) {
67
  $parts = array(
68
    'views_form',
69
    $view->name,
70
    $view->current_display,
71
  );
72

    
73
  return implode('_', $parts);
74
}
75

    
76
/**
77
 * Views will not load plugins advertising a version older than this.
78
 */
79
function views_api_minimum_version() {
80
  return '2';
81
}
82

    
83
/**
84
 * Implements hook_theme().
85
 *
86
 * Register views theming functions.
87
 */
88
function views_theme($existing, $type, $theme, $path) {
89
  $path = drupal_get_path('module', 'views');
90
  ctools_include('theme', 'views', 'theme');
91

    
92
  // Some quasi clever array merging here.
93
  $base = array(
94
    'file' => 'theme.inc',
95
    'path' => $path . '/theme',
96
  );
97

    
98
  // Our extra version of pager from pager.inc.
99
  $hooks['views_mini_pager'] = $base + array(
100
    'variables' => array(
101
      'tags' => array(),
102
      'element' => 0,
103
      'parameters' => array(),
104
    ),
105
    'pattern' => 'views_mini_pager__',
106
  );
107

    
108
  $variables = array(
109
    // For displays, we pass in a dummy array as the first parameter, since
110
    // $view is an object but the core contextual_preprocess() function only
111
    // attaches contextual links when the primary theme argument is an array.
112
    'display' => array('view_array' => array(), 'view' => NULL),
113
    'style' => array(
114
      'view' => NULL,
115
      'options' => NULL,
116
      'rows' => NULL,
117
      'title' => NULL,
118
    ),
119
    'row' => array(
120
      'view' => NULL,
121
      'options' => NULL,
122
      'row' => NULL,
123
      'field_alias' => NULL,
124
    ),
125
    'exposed_form' => array('view' => NULL, 'options' => NULL),
126
    'pager' => array(
127
      'view' => NULL,
128
      'options' => NULL,
129
      'tags' => array(),
130
      'quantity' => 10,
131
      'element' => 0,
132
      'parameters' => array(),
133
    ),
134
  );
135

    
136
  // Default view themes.
137
  $hooks['views_view_field'] = $base + array(
138
    'pattern' => 'views_view_field__',
139
    'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
140
  );
141
  $hooks['views_view_grouping'] = $base + array(
142
    'pattern' => 'views_view_grouping__',
143
    'variables' => array(
144
      'view' => NULL,
145
      'grouping' => NULL,
146
      'grouping_level' => NULL,
147
      'rows' => NULL,
148
      'title' => NULL,
149
    ),
150
  );
151

    
152
  $plugins = views_fetch_plugin_data();
153

    
154
  // Register theme functions for all style plugins.
155
  foreach ($plugins as $type => $info) {
156
    foreach ($info as $plugin => $def) {
157
      if (isset($def['theme']) && (!isset($def['register theme']) || !empty($def['register theme']))) {
158
        $hooks[$def['theme']] = array(
159
          'pattern' => $def['theme'] . '__',
160
          'file' => $def['theme file'],
161
          'path' => $def['theme path'],
162
          'variables' => $variables[$type],
163
        );
164

    
165
        $include = DRUPAL_ROOT . '/' . $def['theme path'] . '/' . $def['theme file'];
166
        if (file_exists($include)) {
167
          require_once $include;
168
        }
169

    
170
        if (!function_exists('theme_' . $def['theme'])) {
171
          $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
172
        }
173
      }
174
      if (isset($def['additional themes'])) {
175
        foreach ($def['additional themes'] as $theme => $theme_type) {
176
          if (empty($theme_type)) {
177
            $theme = $theme_type;
178
            $theme_type = $type;
179
          }
180

    
181
          $hooks[$theme] = array(
182
            'pattern' => $theme . '__',
183
            'file' => $def['theme file'],
184
            'path' => $def['theme path'],
185
            'variables' => $variables[$theme_type],
186
          );
187

    
188
          if (!function_exists('theme_' . $theme)) {
189
            $hooks[$theme]['template'] = drupal_clean_css_identifier($theme);
190
          }
191
        }
192
      }
193
    }
194
  }
195

    
196
  $hooks['views_form_views_form'] = $base + array(
197
    'render element' => 'form',
198
  );
199

    
200
  $hooks['views_exposed_form'] = $base + array(
201
    'template' => 'views-exposed-form',
202
    'pattern' => 'views_exposed_form__',
203
    'render element' => 'form',
204
  );
205

    
206
  $hooks['views_more'] = $base + array(
207
    'template' => 'views-more',
208
    'pattern' => 'views_more__',
209
    'variables' => array(
210
      'more_url' => NULL,
211
      'link_text' => 'more',
212
      'view' => NULL,
213
    ),
214
  );
215

    
216
  // Add theme suggestions which are part of modules.
217
  foreach (views_get_module_apis() as $info) {
218
    if (isset($info['template path'])) {
219
      $hooks += _views_find_module_templates($hooks, $info['template path']);
220
    }
221
  }
222
  return $hooks;
223
}
224

    
225
/**
226
 * Scans a directory of a module for template files.
227
 *
228
 * @param array $cache
229
 *   The existing cache of theme hooks to test against.
230
 * @param string $path
231
 *   The path to search.
232
 *
233
 * @see drupal_find_theme_templates()
234
 */
235
function _views_find_module_templates($cache, $path) {
236
  $templates = array();
237
  $regex = '/\.tpl\.php$/';
238

    
239
  // Because drupal_system_listing works the way it does, we check for real
240
  // templates separately from checking for patterns.
241
  $files = drupal_system_listing($regex, $path, 'name', 0);
242
  foreach ($files as $template => $file) {
243
    // Chop off the remaining extensions if there are any. $template already
244
    // has the rightmost extension removed, but there might still be more,
245
    // such as with .tpl.php, which still has .tpl in $template at this point.
246
    if (($pos = strpos($template, '.')) !== FALSE) {
247
      $template = substr($template, 0, $pos);
248
    }
249
    // Transform - in filenames to _ to match function naming scheme
250
    // for the purposes of searching.
251
    $hook = strtr($template, '-', '_');
252
    if (isset($cache[$hook])) {
253
      $templates[$hook] = array(
254
        'template' => $template,
255
        'path' => dirname($file->filename),
256
        'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL,
257
      );
258
    }
259
    // Ensure that the pattern is maintained from base themes to its sub-themes.
260
    // Each sub-theme will have their templates scanned so the pattern must be
261
    // held for subsequent runs.
262
    if (isset($cache[$hook]['pattern'])) {
263
      $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
264
    }
265
  }
266

    
267
  $patterns = array_keys($files);
268

    
269
  foreach ($cache as $hook => $info) {
270
    if (!empty($info['pattern'])) {
271
      // Transform _ in pattern to - to match file naming scheme
272
      // for the purposes of searching.
273
      $pattern = strtr($info['pattern'], '_', '-');
274

    
275
      $matches = preg_grep('/^' . $pattern . '/', $patterns);
276
      if ($matches) {
277
        foreach ($matches as $match) {
278
          $file = substr($match, 0, strpos($match, '.'));
279
          // Put the underscores back in for the hook name and register this
280
          // pattern.
281
          $templates[strtr($file, '-', '_')] = array(
282
            'template' => $file,
283
            'path' => dirname($files[$match]->uri),
284
            'variables' => isset($info['variables']) ? $info['variables'] : NULL,
285
            'render element' => isset($info['render element']) ? $info['render element'] : NULL,
286
            'base hook' => $hook,
287
            'includes' => isset($info['includes']) ? $info['includes'] : NULL,
288
          );
289
        }
290
      }
291
    }
292
  }
293

    
294
  return $templates;
295
}
296

    
297
/**
298
 * Returns a list of plugins and metadata about them.
299
 *
300
 * @return array
301
 *   An array keyed by PLUGIN_TYPE:PLUGIN_NAME, like 'display:page' or
302
 *   'pager:full', containing an array with the following keys:
303
 *   - title: The plugin's title.
304
 *   - type: The plugin type.
305
 *   - module: The module providing the plugin.
306
 *   - views: An array of enabled Views that are currently using this plugin,
307
 *     keyed by machine name.
308
 */
309
function views_plugin_list() {
310
  $plugin_data = views_fetch_plugin_data();
311
  $plugins = array();
312
  foreach (views_get_enabled_views() as $view) {
313
    foreach ($view->display as $display_id => $display) {
314
      foreach ($plugin_data as $type => $info) {
315
        if ($type == 'display' && isset($display->display_plugin)) {
316
          $name = $display->display_plugin;
317
        }
318
        elseif (isset($display->display_options["{$type}_plugin"])) {
319
          $name = $display->display_options["{$type}_plugin"];
320
        }
321
        elseif (isset($display->display_options[$type]['type'])) {
322
          $name = $display->display_options[$type]['type'];
323
        }
324
        else {
325
          continue;
326
        }
327

    
328
        // Key first by the plugin type, then the name.
329
        $key = $type . ':' . $name;
330
        // Add info for this plugin.
331
        if (!isset($plugins[$key])) {
332
          $plugins[$key] = array(
333
            'type' => $type,
334
            'title' => check_plain($info[$name]['title']),
335
            'module' => check_plain($info[$name]['module']),
336
            'views' => array(),
337
          );
338
        }
339

    
340
        // Add this view to the list for this plugin.
341
        $plugins[$key]['views'][$view->name] = $view->name;
342
      }
343
    }
344
  }
345
  return $plugins;
346
}
347

    
348
/**
349
 * Preprocess a node.
350
 *
351
 * A theme preprocess function to automatically allow view-based node
352
 * templates if called from a view.
353
 *
354
 * The 'modules/node.views.inc' file is a better place for this, but
355
 * we haven't got a chance to load that file before Drupal builds the
356
 * node portion of the theme registry.
357
 */
358
function views_preprocess_node(&$vars) {
359
  // The 'view' attribute of the node is added in views_preprocess_node()
360
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
361
    $vars['view'] = $vars['node']->view;
362
    $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name;
363
    if (!empty($vars['node']->view->current_display)) {
364
      $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
365

    
366
      // If a node is being rendered in a view, and the view does not have a
367
      // path, prevent drupal from accidentally setting the $page variable.
368
      if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
369
        $vars['page'] = FALSE;
370
      }
371
    }
372
  }
373

    
374
  // Allow to alter comments and links based on the settings in the row plugin.
375
  if (!empty($vars['view']->style_plugin->row_plugin) && get_class($vars['view']->style_plugin->row_plugin) == 'views_plugin_row_node_view') {
376
    node_row_node_view_preprocess_node($vars);
377
  }
378
}
379

    
380
/**
381
 * Preprocess a comment.
382
 *
383
 * A theme preprocess function to automatically allow view-based node
384
 * templates if called from a view.
385
 */
386
function views_preprocess_comment(&$vars) {
387
  // The 'view' attribute of the node is added in
388
  // template_preprocess_views_view_row_comment().
389
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
390
    $vars['view'] = &$vars['node']->view;
391
    $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name;
392
    if (!empty($vars['node']->view->current_display)) {
393
      $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
394
    }
395
  }
396
}
397

    
398
/**
399
 * Implements hook_permission().
400
 */
401
function views_permission() {
402
  return array(
403
    'administer views' => array(
404
      'title' => t('Administer views'),
405
      'description' => t('Access the views administration pages.'),
406
      'restrict access' => TRUE,
407
    ),
408
    'access all views' => array(
409
      'title' => t('Bypass views access control'),
410
      'description' => t('Bypass access control when accessing views.'),
411
      'restrict access' => TRUE,
412
    ),
413
  );
414
}
415

    
416
/**
417
 * Implements hook_menu().
418
 */
419
function views_menu() {
420
  $items = array();
421
  $items['views/ajax'] = array(
422
    'title' => 'Views',
423
    'page callback' => 'views_ajax',
424
    'theme callback' => 'ajax_base_page_theme',
425
    'delivery callback' => 'ajax_deliver',
426
    'access callback' => TRUE,
427
    'description' => 'Ajax callback for view loading.',
428
    'type' => MENU_CALLBACK,
429
    'file' => 'includes/ajax.inc',
430
  );
431
  // Path is not admin/structure/views due to menu complications with the
432
  // wildcards from the generic ajax callback.
433
  $items['admin/views/ajax/autocomplete/user'] = array(
434
    'page callback' => 'views_ajax_autocomplete_user',
435
    'theme callback' => 'ajax_base_page_theme',
436
    'access callback' => 'user_access',
437
    'access arguments' => array('access user profiles'),
438
    'type' => MENU_CALLBACK,
439
    'file' => 'includes/ajax.inc',
440
  );
441
  // Define another taxonomy autocomplete because the default one of drupal
442
  // does not support a vid a argument anymore.
443
  $items['admin/views/ajax/autocomplete/taxonomy'] = array(
444
    'page callback' => 'views_ajax_autocomplete_taxonomy',
445
    'theme callback' => 'ajax_base_page_theme',
446
    'access callback' => 'user_access',
447
    'access arguments' => array('access content'),
448
    'type' => MENU_CALLBACK,
449
    'file' => 'includes/ajax.inc',
450
  );
451
  return $items;
452
}
453

    
454
/**
455
 * Implements hook_menu_alter().
456
 */
457
function views_menu_alter(&$callbacks) {
458
  $our_paths = array();
459
  $views = views_get_applicable_views('uses hook menu');
460
  foreach ($views as $data) {
461
    list($view, $display_id) = $data;
462
    $result = $view->execute_hook_menu($display_id, $callbacks);
463
    if (is_array($result)) {
464
      // The menu system doesn't support having two otherwise identical paths
465
      // with different placeholders. So we want to remove the existing items
466
      // from the menu whose paths would conflict with ours. First, we must find
467
      // any existing menu items that may conflict. We use a regular expression
468
      // because we don't know what placeholders they might use. Note that we
469
      // first construct the regex itself by replacing %views_arg in the display
470
      // path, then we use this constructed regex (which will be something like
471
      // '#^(foo/%[^/]*/bar)$#') to search through the existing paths.
472
      $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
473
      $matches = preg_grep($regex, array_keys($callbacks));
474

    
475
      // Remove any conflicting items that were found.
476
      foreach ($matches as $path) {
477
        // Don't remove the paths we just added!
478
        if (!isset($our_paths[$path])) {
479
          unset($callbacks[$path]);
480
        }
481
      }
482
      foreach ($result as $path => $item) {
483
        if (!isset($callbacks[$path])) {
484
          // Add a new item, possibly replacing (and thus effectively
485
          // overriding) one that we removed above.
486
          $callbacks[$path] = $item;
487
        }
488
        else {
489
          // This item already exists, so it must be one that we added.
490
          // We change the various callback arguments to pass an array
491
          // of possible display IDs instead of a single ID.
492
          $callbacks[$path]['page arguments'][1] = (array) $callbacks[$path]['page arguments'][1];
493
          $callbacks[$path]['page arguments'][1][] = $display_id;
494
          $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
495
          $callbacks[$path]['load arguments'][1] = (array) $callbacks[$path]['load arguments'][1];
496
          $callbacks[$path]['load arguments'][1][] = $display_id;
497
        }
498
        $our_paths[$path] = TRUE;
499
      }
500
    }
501
  }
502

    
503
  // Save memory: Destroy those views.
504
  foreach ($views as $data) {
505
    list($view, $display_id) = $data;
506
    $view->destroy();
507
  }
508
}
509

    
510
/**
511
 * Load a views argument.
512
 *
513
 * Helper function for menu loading. This will automatically be
514
 * called in order to 'load' a views argument; primarily it
515
 * will be used to perform validation.
516
 *
517
 * @param string $value
518
 *   The actual value passed.
519
 * @param string $name
520
 *   The name of the view. This needs to be specified in the 'load function'
521
 *   of the menu entry.
522
 * @param string $display_id
523
 *   The display id that will be loaded for this menu item.
524
 * @param int $index
525
 *   The menu argument index. This counts from 1.
526
 */
527
function views_arg_load($value, $name, $display_id, $index) {
528
  static $views = array();
529

    
530
  $display_ids = is_array($display_id) ? $display_id : array($display_id);
531
  $display_id = reset($display_ids);
532

    
533
  foreach ($display_ids as $id) {
534
    // Make sure we haven't already loaded this views argument for a similar
535
    // menu item elsewhere. Since access is always checked for the current user,
536
    // we are sure that the static cache contains valid entries.
537
    $key = $name . ':' . $id . ':' . $value . ':' . $index;
538
    if (isset($views[$key])) {
539
      return $views[$key];
540
    }
541
    // Lazy load the view object to avoid unnecessary work.
542
    if (!isset($view)) {
543
      $view = views_get_view($name);
544
    }
545
    // Pick the first display we have access to.
546
    if ($view && count($display_ids) > 1 && $view->access($id)) {
547
      $display_id = $id;
548
      break;
549
    }
550
  }
551

    
552
  if ($view) {
553
    $view->set_display($display_id);
554
    $view->init_handlers();
555

    
556
    $ids = array_keys($view->argument);
557

    
558
    $indexes = array();
559
    $path = explode('/', $view->get_path());
560

    
561
    foreach ($path as $id => $piece) {
562
      if ($piece == '%' && !empty($ids)) {
563
        $indexes[$id] = array_shift($ids);
564
      }
565
    }
566

    
567
    if (isset($indexes[$index])) {
568
      if (isset($view->argument[$indexes[$index]])) {
569
        $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
570
        $view->destroy();
571

    
572
        // Store the output in case we load this same menu item again.
573
        $views[$key] = $arg;
574
        return $arg;
575
      }
576
    }
577
    $view->destroy();
578
  }
579
}
580

    
581
/**
582
 * Page callback: Displays a page view, given a name and display id.
583
 *
584
 * @param string $name
585
 *   The name of a view.
586
 * @param string $display_id
587
 *   The display id of a view.
588
 *
589
 * @return string|int
590
 *   Either the HTML of a fully-executed view, or MENU_NOT_FOUND.
591
 */
592
function views_page($name, $display_id) {
593
  $args = func_get_args();
594
  // Remove $name and $display_id from the arguments.
595
  array_shift($args);
596
  array_shift($args);
597

    
598
  // Load the view and render it.
599
  if ($view = views_get_view($name)) {
600
    return $view->execute_display($display_id, $args);
601
  }
602

    
603
  // Fallback; if we get here no view was found or handler was not valid.
604
  return MENU_NOT_FOUND;
605
}
606

    
607
/**
608
 * Implements hook_page_alter().
609
 */
610
function views_page_alter(&$page) {
611
  // If the main content of this page contains a view, attach its contextual
612
  // links to the overall page array. This allows them to be rendered directly
613
  // next to the page title.
614
  $view = views_get_page_view();
615
  if (!empty($view)) {
616
    // If a module is still putting in the display like we used to, catch that.
617
    if (is_subclass_of($view, 'views_plugin_display')) {
618
      $view = $view->view;
619
    }
620

    
621
    views_add_contextual_links($page, 'page', $view, $view->current_display);
622
  }
623
}
624

    
625
/**
626
 * Implements MODULE_preprocess_HOOK() for html.tpl.php.
627
 */
628
function views_preprocess_html(&$variables) {
629
  // If the page contains a view as its main content, contextual links may have
630
  // been attached to the page as a whole; for example, by views_page_alter().
631
  // This allows them to be associated with the page and rendered by default
632
  // next to the page title (which we want). However, it also causes the
633
  // Contextual Links module to treat the wrapper for the entire page (i.e.,
634
  // the <body> tag) as the HTML element that these contextual links are
635
  // associated with. This we don't want; for better visual highlighting, we
636
  // prefer a smaller region to be chosen. The region we prefer differs from
637
  // theme to theme and depends on the details of the theme's markup in
638
  // page.tpl.php, so we can only find it using JavaScript. We therefore remove
639
  // the "contextual-links-region" class from the <body> tag here and add
640
  // JavaScript that will insert it back in the correct place.
641
  if (!empty($variables['page']['#views_contextual_links_info'])) {
642
    $key = array_search('contextual-links-region', $variables['classes_array']);
643
    if ($key !== FALSE) {
644
      $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
645
      // Add the JavaScript, with a group and weight such that it will run
646
      // before modules/contextual/contextual.js.
647
      drupal_add_js(drupal_get_path('module', 'views') . '/js/views-contextual.js', array('group' => JS_LIBRARY, 'weight' => -1));
648
    }
649
  }
650
}
651

    
652
/**
653
 * Implements hook_preprocess_HOOK() for page.tpl.php.
654
 */
655
function views_preprocess_page(&$variables) {
656
  // If the page contains a view as its main content, contextual links may have
657
  // been attached to the page as a whole; for example, by views_page_alter().
658
  // This allows them to be associated with the page and rendered by default
659
  // next to the page title (which we want). However, it also causes the
660
  // Contextual Links module to treat the wrapper for the entire page (i.e.,
661
  // the <body> tag) as the HTML element that these contextual links are
662
  // associated with. This we don't want; for better visual highlighting, we
663
  // prefer a smaller region to be chosen. The region we prefer differs from
664
  // theme to theme and depends on the details of the theme's markup in
665
  // page.tpl.php, so we can only find it using JavaScript. We therefore remove
666
  // the "contextual-links-region" class from the <body> tag here and add
667
  // JavaScript that will insert it back in the correct place.
668
  if (!empty($variables['page']['#views_contextual_links_info'])) {
669
    $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
670
  }
671
}
672

    
673
/**
674
 * Implements hook_contextual_links_view_alter().
675
 */
676
function views_contextual_links_view_alter(&$element, $items) {
677
  // If we are rendering views-related contextual links attached to the overall
678
  // page array, add a class to the list of contextual links. This will be used
679
  // by the JavaScript added in views_preprocess_html().
680
  if (!empty($element['#element']['#views_contextual_links_info']) && !empty($element['#element']['#type']) && $element['#element']['#type'] == 'page') {
681
    $element['#attributes']['class'][] = 'views-contextual-links-page';
682
  }
683
}
684

    
685
/**
686
 * Implements hook_block_info().
687
 */
688
function views_block_info() {
689
  // Try to avoid instantiating all the views just to get the blocks info.
690
  views_include('cache');
691
  $cache = views_cache_get('views_block_items', TRUE);
692
  if ($cache && is_array($cache->data)) {
693
    return $cache->data;
694
  }
695

    
696
  $items = array();
697
  $views = views_get_all_views();
698
  foreach ($views as $view) {
699
    // Disabled views get nothing.
700
    if (!empty($view->disabled)) {
701
      continue;
702
    }
703

    
704
    $view->init_display();
705
    foreach ($view->display as $display_id => $display) {
706

    
707
      if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
708
        $result = $display->handler->execute_hook_block_list();
709
        if (is_array($result)) {
710
          $items = array_merge($items, $result);
711
        }
712
      }
713

    
714
      if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
715
        $result = $display->handler->get_special_blocks();
716
        if (is_array($result)) {
717
          $items = array_merge($items, $result);
718
        }
719
      }
720
    }
721
  }
722

    
723
  // block.module has a delta length limit of 32, but our deltas can
724
  // unfortunately be longer because view names can be 32 and display IDs
725
  // can also be 32. So for very long deltas, change to md5 hashes.
726
  $hashes = array();
727

    
728
  // Get the keys because we're modifying the array and we don't want to
729
  // confuse PHP too much.
730
  $keys = array_keys($items);
731
  foreach ($keys as $delta) {
732
    if (strlen($delta) >= 32) {
733
      $hash = md5($delta);
734
      $hashes[$hash] = $delta;
735
      $items[$hash] = $items[$delta];
736
      unset($items[$delta]);
737
    }
738
  }
739

    
740
  // Only save hashes if they have changed.
741
  $old_hashes = variable_get('views_block_hashes', array());
742
  if ($hashes != $old_hashes) {
743
    variable_set('views_block_hashes', $hashes);
744
  }
745
  // Save memory: Destroy those views.
746
  foreach ($views as $view) {
747
    $view->destroy();
748
  }
749

    
750
  views_cache_set('views_block_items', $items, TRUE);
751

    
752
  return $items;
753
}
754

    
755
/**
756
 * Implements hook_block_view().
757
 */
758
function views_block_view($delta) {
759
  // If this is 32, this should be an md5 hash.
760
  if (strlen($delta) == 32) {
761
    $hashes = variable_get('views_block_hashes', array());
762
    if (!empty($hashes[$delta])) {
763
      $delta = $hashes[$delta];
764
    }
765
  }
766

    
767
  // This indicates it's a special one.
768
  if (substr($delta, 0, 1) == '-') {
769
    list($nothing, $type, $name, $display_id) = explode('-', $delta);
770
    // Put the - back on.
771
    $type = '-' . $type;
772
    if ($view = views_get_view($name)) {
773
      if ($view->access($display_id)) {
774
        $view->set_display($display_id);
775
        if (isset($view->display_handler)) {
776
          $output = $view->display_handler->view_special_blocks($type);
777
          // Before returning the block output, convert it to a renderable
778
          // array with contextual links.
779
          views_add_block_contextual_links($output, $view, $display_id, 'special_block_' . $type);
780
          $view->destroy();
781
          return $output;
782
        }
783
      }
784
      $view->destroy();
785
    }
786
  }
787

    
788
  // If the delta doesn't contain valid data return nothing.
789
  $explode = explode('-', $delta);
790
  if (count($explode) != 2) {
791
    return;
792
  }
793
  list($name, $display_id) = $explode;
794
  // Load the view.
795
  if ($view = views_get_view($name)) {
796
    if ($view->access($display_id)) {
797
      $output = $view->execute_display($display_id);
798
      // Before returning the block output, convert it to a renderable array
799
      // with contextual links.
800
      views_add_block_contextual_links($output, $view, $display_id);
801
      $view->destroy();
802
      return $output;
803
    }
804
    $view->destroy();
805
  }
806
}
807

    
808
/**
809
 * Converts Views block content to a renderable array with contextual links.
810
 *
811
 * @param array $block
812
 *   An array representing the block, with the same structure as the return
813
 *   value of hook_block_view(). This will be modified so as to force
814
 *   $block['content'] to be a renderable array, containing the optional
815
 *   '#contextual_links' property (if there are any contextual links associated
816
 *   with the block).
817
 * @param object $view
818
 *   The view that was used to generate the block content.
819
 * @param string $display_id
820
 *   The ID of the display within the view that was used to generate the block
821
 *   content.
822
 * @param string $block_type
823
 *   The type of the block. If it's block it's a regular views display,
824
 *   but 'special_block_-exp' exist as well.
825
 */
826
function views_add_block_contextual_links(&$block, $view, $display_id, $block_type = 'block') {
827
  // Do not add contextual links to an empty block.
828
  if (!empty($block['content'])) {
829
    // Contextual links only work on blocks whose content is a renderable
830
    // array, so if the block contains a string of already-rendered markup,
831
    // convert it to an array.
832
    if (is_string($block['content'])) {
833
      $block['content'] = array('#markup' => $block['content']);
834
    }
835
    // Add the contextual links.
836
    views_add_contextual_links($block['content'], $block_type, $view, $display_id);
837
  }
838
}
839

    
840
/**
841
 * Adds contextual links associated with a view display to a renderable array.
842
 *
843
 * This function should be called when a view is being rendered in a particular
844
 * location and you want to attach the appropriate contextual links (e.g.,
845
 * links for editing the view) to it.
846
 *
847
 * The function operates by checking the view's display plugin to see if it has
848
 * defined any contextual links that are intended to be displayed in the
849
 * requested location; if so, it attaches them. The contextual links intended
850
 * for a particular location are defined by the 'contextual links' and
851
 * 'contextual links locations' properties in hook_views_plugins() and
852
 * hook_views_plugins_alter(); as a result, these hook implementations have
853
 * full control over where and how contextual links are rendered for each
854
 * display.
855
 *
856
 * In addition to attaching the contextual links to the passed-in array (via
857
 * the standard #contextual_links property), this function also attaches
858
 * additional information via the #views_contextual_links_info property. This
859
 * stores an array whose keys are the names of each module that provided
860
 * views-related contextual links (same as the keys of the #contextual_links
861
 * array itself) and whose values are themselves arrays whose keys ('location',
862
 * 'view_name', and 'view_display_id') store the location, name of the view,
863
 * and display ID that were passed in to this function. This allows you to
864
 * access information about the contextual links and how they were generated in
865
 * a variety of contexts where you might be manipulating the renderable array
866
 * later on (for example, alter hooks which run later during the same page
867
 * request).
868
 *
869
 * @param array $render_element
870
 *   The renderable array to which contextual links will be added. This array
871
 *   should be suitable for passing in to drupal_render() and will normally
872
 *   contain a representation of the view display whose contextual links are
873
 *   being requested.
874
 * @param string $location
875
 *   The location in which the calling function intends to render the view and
876
 *   its contextual links. The core system supports three options for this
877
 *   parameter:
878
 *   - 'block': Used when rendering a block which contains a view. This
879
 *     retrieves any contextual links intended to be attached to the block
880
 *     itself.
881
 *   - 'page': Used when rendering the main content of a page which contains a
882
 *     view. This retrieves any contextual links intended to be attached to the
883
 *     page itself (for example, links which are displayed directly next to the
884
 *     page title).
885
 *   - 'view': Used when rendering the view itself, in any context. This
886
 *     retrieves any contextual links intended to be attached directly to the
887
 *     view.
888
 *   If you are rendering a view and its contextual links in another location,
889
 *   you can pass in a different value for this parameter. However, you will
890
 *   also need to use hook_views_plugins() or hook_views_plugins_alter() to
891
 *   declare, via the 'contextual links locations' array key, which view
892
 *   displays support having their contextual links rendered in the location
893
 *   you have defined.
894
 * @param object $view
895
 *   The view whose contextual links will be added.
896
 * @param string $display_id
897
 *   The ID of the display within $view whose contextual links will be added.
898
 *
899
 * @see hook_views_plugins()
900
 * @see views_block_view()
901
 * @see views_page_alter()
902
 * @see template_preprocess_views_view()
903
 */
904
function views_add_contextual_links(&$render_element, $location, $view, $display_id) {
905
  // Do not do anything if the view is configured to hide its administrative
906
  // links.
907
  if (empty($view->hide_admin_links)) {
908
    // Also do not do anything if the display plugin has not defined any
909
    // contextual links that are intended to be displayed in the requested
910
    // location.
911
    $plugin = views_fetch_plugin_data('display', $view->display[$display_id]->display_plugin);
912
    // If contextual links locations are not set, provide a sane default. (To
913
    // avoid displaying any contextual links at all, a display plugin can still
914
    // set 'contextual links locations' to, e.g., an empty array).
915
    $plugin += array('contextual links locations' => array('view'));
916
    // On exposed_forms blocks contextual links should always be visible.
917
    $plugin['contextual links locations'][] = 'special_block_-exp';
918
    $has_links = !empty($plugin['contextual links']) && !empty($plugin['contextual links locations']);
919
    if ($has_links && in_array($location, $plugin['contextual links locations'])) {
920
      foreach ($plugin['contextual links'] as $module => $link) {
921
        $args = array();
922
        $valid = TRUE;
923
        if (!empty($link['argument properties'])) {
924
          foreach ($link['argument properties'] as $property) {
925
            // If the plugin is trying to create an invalid contextual link
926
            // (for example, "path/to/{$view->property}", where $view->property
927
            // does not exist), we cannot construct the link, so we skip it.
928
            if (!property_exists($view, $property)) {
929
              $valid = FALSE;
930
              break;
931
            }
932
            else {
933
              $args[] = $view->{$property};
934
            }
935
          }
936
        }
937
        // If the link was valid, attach information about it to the renderable
938
        // array.
939
        if ($valid) {
940
          $render_element['#contextual_links'][$module] = array($link['parent path'], $args);
941
          $render_element['#views_contextual_links_info'][$module] = array(
942
            'location' => $location,
943
            'view' => $view,
944
            'view_name' => $view->name,
945
            'view_display_id' => $display_id,
946
          );
947
        }
948
      }
949
    }
950
  }
951
}
952

    
953
/**
954
 * Returns an array of language names.
955
 *
956
 * This is a one to one copy of locale_language_list because we can't rely on
957
 * enabled locale module.
958
 *
959
 * @param string $field
960
 *   Either 'name' for localized names in current language or 'native' for
961
 *   native names.
962
 * @param bool $all
963
 *   Boolean to return all languages or only enabled ones.
964
 *
965
 * @see locale_language_list()
966
 */
967
function views_language_list($field = 'name', $all = FALSE) {
968
  if ($all) {
969
    $languages = language_list();
970
  }
971
  else {
972
    $languages = language_list('enabled');
973
    $languages = $languages[1];
974
  }
975
  $list = array();
976
  foreach ($languages as $language) {
977
    $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
978
  }
979
  return $list;
980
}
981

    
982
/**
983
 * Implements hook_flush_caches().
984
 */
985
function views_flush_caches() {
986
  return array('cache_views', 'cache_views_data');
987
}
988

    
989
/**
990
 * Implements hook_field_create_instance().
991
 */
992
function views_field_create_instance($instance) {
993
  cache_clear_all('*', 'cache_views', TRUE);
994
  cache_clear_all('*', 'cache_views_data', TRUE);
995
}
996

    
997
/**
998
 * Implements hook_field_update_instance().
999
 */
1000
function views_field_update_instance($instance, $prior_instance) {
1001
  cache_clear_all('*', 'cache_views', TRUE);
1002
  cache_clear_all('*', 'cache_views_data', TRUE);
1003
}
1004

    
1005
/**
1006
 * Implements hook_field_delete_instance().
1007
 */
1008
function views_field_delete_instance($instance) {
1009
  cache_clear_all('*', 'cache_views', TRUE);
1010
  cache_clear_all('*', 'cache_views_data', TRUE);
1011
}
1012

    
1013
/**
1014
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
1015
 */
1016
function views_invalidate_cache() {
1017
  // Clear the views cache.
1018
  cache_clear_all('*', 'cache_views', TRUE);
1019

    
1020
  // Clear the page and block cache.
1021
  cache_clear_all();
1022

    
1023
  // Set the menu as needed to be rebuilt.
1024
  variable_set('menu_rebuild_needed', TRUE);
1025

    
1026
  // Allow modules to respond to the Views cache being cleared.
1027
  module_invoke_all('views_invalidate_cache');
1028
}
1029

    
1030
/**
1031
 * Access callback to determine if the user can import Views.
1032
 *
1033
 * View imports require an additional access check because they are PHP
1034
 * code and PHP is more locked down than administer views.
1035
 */
1036
function views_import_access() {
1037
  return user_access('administer views') && user_access('use PHP for settings');
1038
}
1039

    
1040
/**
1041
 * Determine if the logged in user has access to a view.
1042
 *
1043
 * This function should only be called from a menu hook or some other
1044
 * embedded source. Each argument is the result of a call to
1045
 * views_plugin_access::get_access_callback() which is then used
1046
 * to determine if that display is accessible. If *any* argument
1047
 * is accessible, then the view is accessible.
1048
 */
1049
function views_access() {
1050
  $args = func_get_args();
1051
  foreach ($args as $arg) {
1052
    if ($arg === TRUE) {
1053
      return TRUE;
1054
    }
1055

    
1056
    if (!is_array($arg)) {
1057
      continue;
1058
    }
1059

    
1060
    list($callback, $arguments) = $arg;
1061
    $arguments = $arguments ? $arguments : array();
1062
    // Bring dynamic arguments to the access callback.
1063
    foreach ($arguments as $key => $value) {
1064
      if (is_int($value) && isset($args[$value])) {
1065
        $arguments[$key] = $args[$value];
1066
      }
1067
    }
1068
    if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
1069
      return TRUE;
1070
    }
1071
  }
1072

    
1073
  return FALSE;
1074
}
1075

    
1076
/**
1077
 * Access callback for the views_plugin_access_perm access plugin.
1078
 *
1079
 * Determine if the specified user has access to a view on the basis of
1080
 * permissions. If the $account argument is omitted, the current user
1081
 * is used.
1082
 */
1083
function views_check_perm($perms, $account = NULL) {
1084
  // Backward compatibility to ensure also a single permission string is
1085
  // properly processed.
1086
  $perms = is_array($perms) ? $perms : array($perms);
1087
  if (user_access('access all views', $account)) {
1088
    return TRUE;
1089
  }
1090
  // Perms are handled as OR, as soon one permission allows access TRUE is
1091
  // returned.
1092
  foreach ($perms as $perm) {
1093
    if (user_access($perm, $account)) {
1094
      return TRUE;
1095
    }
1096
  }
1097
  return FALSE;
1098
}
1099

    
1100
/**
1101
 * Access callback for the views_plugin_access_role access plugin.
1102
 *
1103
 * Determine if the specified user has access to a view on the basis of any of
1104
 * the requested roles. If the $account argument is omitted, the current user
1105
 * is used.
1106
 */
1107
function views_check_roles($rids, $account = NULL) {
1108
  global $user;
1109
  $account = isset($account) ? $account : $user;
1110
  $roles = array_keys($account->roles);
1111
  $roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
1112
  return user_access('access all views', $account) || array_intersect(array_filter($rids), $roles);
1113
}
1114

    
1115
/**
1116
 * Set page view.
1117
 *
1118
 * Set the current 'page view' that is being displayed so that it is easy
1119
 * for other modules or the theme to identify.
1120
 */
1121
function &views_set_page_view($view = NULL) {
1122
  static $cache = NULL;
1123
  if (isset($view)) {
1124
    $cache = $view;
1125
  }
1126

    
1127
  return $cache;
1128
}
1129

    
1130
/**
1131
 * Get page view.
1132
 *
1133
 * Find out what, if any, page view is currently in use. Please note that
1134
 * this returns a reference, so be careful! You can unintentionally modify the
1135
 * $view object.
1136
 *
1137
 * @return view
1138
 *   A fully formed, empty $view object.
1139
 */
1140
function &views_get_page_view() {
1141
  return views_set_page_view();
1142
}
1143

    
1144
/**
1145
 * Set current view.
1146
 *
1147
 * Set the current 'current view' that is being built/rendered so that it is
1148
 * easy for other modules or items in drupal_eval to identify.
1149
 *
1150
 * @return view
1151
 *   The current view.
1152
 */
1153
function &views_set_current_view($view = NULL) {
1154
  static $cache = NULL;
1155
  if (isset($view)) {
1156
    $cache = $view;
1157
  }
1158

    
1159
  return $cache;
1160
}
1161

    
1162
/**
1163
 * Get current view.
1164
 *
1165
 * Find out what, if any, current view is currently in use. Please note that
1166
 * this returns a reference, so be careful! You can unintentionally modify the
1167
 * $view object.
1168
 *
1169
 * @return view
1170
 *   The current view.
1171
 */
1172
function &views_get_current_view() {
1173
  return views_set_current_view();
1174
}
1175

    
1176
/**
1177
 * Include views .inc files as necessary.
1178
 */
1179
function views_include($file) {
1180
  static $views_path;
1181
  if (!isset($views_path)) {
1182
    $views_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'views');
1183
  }
1184
  include_once $views_path . '/includes/' . $file . '.inc';
1185
}
1186

    
1187
/**
1188
 * Load views files on behalf of modules.
1189
 */
1190
function views_module_include($api, $reset = FALSE) {
1191
  if ($reset) {
1192
    $cache = &drupal_static('ctools_plugin_api_info');
1193
    if (isset($cache['views']['views'])) {
1194
      unset($cache['views']['views']);
1195
    }
1196
  }
1197
  ctools_include('plugins');
1198
  return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version());
1199
}
1200

    
1201
/**
1202
 * Get a list of modules that support the current views API.
1203
 */
1204
function views_get_module_apis($api = 'views', $reset = FALSE) {
1205
  if ($reset) {
1206
    $cache = &drupal_static('ctools_plugin_api_info');
1207
    if (isset($cache['views']['views'])) {
1208
      unset($cache['views']['views']);
1209
    }
1210
  }
1211
  ctools_include('plugins');
1212
  return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version());
1213
}
1214

    
1215
/**
1216
 * Include views .css files.
1217
 */
1218
function views_add_css($file) {
1219
  // We set preprocess to FALSE because we are adding the files conditionally,
1220
  // and we don't want to generate duplicate cache files.
1221
  // @todo at some point investigate adding some files unconditionally and
1222
  // allowing preprocess.
1223
  drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE));
1224
}
1225

    
1226
/**
1227
 * Include views .js files.
1228
 */
1229
function views_add_js($file) {
1230
  // If javascript has been disabled by the user, never add js files.
1231
  if (variable_get('views_no_javascript', FALSE)) {
1232
    return;
1233
  }
1234
  static $base = TRUE, $ajax = TRUE;
1235
  if ($base) {
1236
    drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
1237
    $base = FALSE;
1238
  }
1239
  if ($ajax && in_array($file, array('ajax', 'ajax_view'))) {
1240
    drupal_add_library('system', 'drupal.ajax');
1241
    drupal_add_library('system', 'jquery.form');
1242
    $ajax = FALSE;
1243
  }
1244
  ctools_add_js($file, 'views');
1245
}
1246

    
1247
/**
1248
 * Load views files on behalf of modules.
1249
 */
1250
function views_include_handlers($reset = FALSE) {
1251
  static $finished = FALSE;
1252
  // Ensure this only gets run once.
1253
  if ($finished && !$reset) {
1254
    return;
1255
  }
1256

    
1257
  views_include('base');
1258
  views_include('handlers');
1259
  views_include('cache');
1260
  views_include('plugins');
1261
  views_module_include('views', $reset);
1262
  $finished = TRUE;
1263
}
1264

    
1265
/**
1266
 * Fetch a handler from the data cache.
1267
 *
1268
 * @param string $table
1269
 *   The name of the table this handler is from.
1270
 * @param string $field
1271
 *   The name of the field this handler is from.
1272
 * @param string $key
1273
 *   The type of handler. i.e, sort, field, argument, filter, relationship.
1274
 * @param mixed $override
1275
 *   Override the actual handler object with this class. Used for aggregation
1276
 *   when the handler is redirected to the aggregation handler.
1277
 *
1278
 * @return views_handler
1279
 *   An instance of a handler object. May be views_handler_broken.
1280
 */
1281
function views_get_handler($table, $field, $key, $override = NULL) {
1282
  static $recursion_protection = array();
1283

    
1284
  $data = views_fetch_data($table, FALSE);
1285
  $handler = NULL;
1286
  views_include('handlers');
1287

    
1288
  // Support conversion on table level.
1289
  if (isset($data['moved to'])) {
1290
    $moved = array($data['moved to'], $field);
1291
  }
1292
  // Support conversion on datafield level.
1293
  if (isset($data[$field]['moved to'])) {
1294
    $moved = $data[$field]['moved to'];
1295
  }
1296
  // Support conversion on handler level.
1297
  if (isset($data[$field][$key]['moved to'])) {
1298
    $moved = $data[$field][$key]['moved to'];
1299
  }
1300

    
1301
  if (isset($data[$field][$key]) || !empty($moved)) {
1302
    if (!empty($moved)) {
1303
      list($moved_table, $moved_field) = $moved;
1304
      if (!empty($recursion_protection[$moved_table][$moved_field])) {
1305
        // Recursion detected!
1306
        return NULL;
1307
      }
1308

    
1309
      $recursion_protection[$moved_table][$moved_field] = TRUE;
1310
      $handler = views_get_handler($moved_table, $moved_field, $key, $override);
1311
      $recursion_protection = array();
1312
      if ($handler) {
1313
        // Store these values so we know what we were originally called.
1314
        $handler->original_table = $table;
1315
        $handler->original_field = $field;
1316
        if (empty($handler->actual_table)) {
1317
          $handler->actual_table = $moved_table;
1318
          $handler->actual_field = $moved_field;
1319
        }
1320
      }
1321
      return $handler;
1322
    }
1323

    
1324
    // Set up a default handler.
1325
    if (empty($data[$field][$key]['handler'])) {
1326
      $data[$field][$key]['handler'] = 'views_handler_' . $key;
1327
    }
1328

    
1329
    if ($override) {
1330
      $data[$field][$key]['override handler'] = $override;
1331
    }
1332

    
1333
    $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
1334
  }
1335

    
1336
  if ($handler) {
1337
    return $handler;
1338
  }
1339

    
1340
  // DEBUG -- identify missing handlers.
1341
  $placeholders = array('@table' => $table, '@field' => $field, '@key' => $key);
1342
  vpr("Missing handler: @table @field @key", $placeholders);
1343
  $broken = array(
1344
    'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
1345
    'handler' => 'views_handler_' . $key . '_broken',
1346
    'table' => $table,
1347
    'field' => $field,
1348
  );
1349
  return _views_create_handler($broken, 'handler', $key);
1350
}
1351

    
1352
/**
1353
 * Fetch Views' data from the cache.
1354
 */
1355
function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
1356
  views_include('cache');
1357
  return _views_fetch_data($table, $move, $reset);
1358
}
1359

    
1360
/**
1361
 * Fetch the plugin data from cache.
1362
 */
1363
function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
1364
  views_include('cache');
1365
  return _views_fetch_plugin_data($type, $plugin, $reset);
1366
}
1367

    
1368
/**
1369
 * Fetch a list of all base tables available.
1370
 *
1371
 * @param string $type
1372
 *   Either 'display', 'style' or 'row'.
1373
 * @param string $key
1374
 *   For style plugins, this is an optional type to restrict to. May be
1375
 *   'normal', 'summary', 'feed' or others based on the needs of the display.
1376
 * @param array $base
1377
 *   An array of possible base tables.
1378
 *
1379
 * @return array
1380
 *   A keyed array of in the form of 'base_table' => 'Description'.
1381
 */
1382
function views_fetch_plugin_names($type, $key = NULL, $base = array()) {
1383
  $data = views_fetch_plugin_data();
1384

    
1385
  $plugins[$type] = array();
1386

    
1387
  foreach ($data[$type] as $id => $plugin) {
1388
    // Skip plugins that don't conform to our key.
1389
    if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
1390
      continue;
1391
    }
1392
    if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) {
1393
      $plugins[$type][$id] = $plugin['title'];
1394
    }
1395
  }
1396

    
1397
  if (!empty($plugins[$type])) {
1398
    asort($plugins[$type]);
1399
    return $plugins[$type];
1400
  }
1401

    
1402
  // Fall-through.
1403
  return array();
1404
}
1405

    
1406
/**
1407
 * Get a handler for a plugin.
1408
 *
1409
 * @return views_plugin
1410
 *   The created plugin object.
1411
 */
1412
function views_get_plugin($type, $plugin, $reset = FALSE) {
1413
  views_include('handlers');
1414
  $definition = views_fetch_plugin_data($type, $plugin, $reset);
1415
  if (!empty($definition)) {
1416
    return _views_create_handler($definition, $type);
1417
  }
1418
}
1419

    
1420
/**
1421
 * Load the current enabled localization plugin.
1422
 *
1423
 * @return string
1424
 *   The name of the localization plugin.
1425
 */
1426
function views_get_localization_plugin() {
1427
  $plugin = variable_get('views_localization_plugin', '');
1428
  // Provide sane default values for the localization plugin.
1429
  if (empty($plugin)) {
1430
    if (module_exists('locale')) {
1431
      $plugin = 'core';
1432
    }
1433
    else {
1434
      $plugin = 'none';
1435
    }
1436
  }
1437

    
1438
  return $plugin;
1439
}
1440

    
1441
/**
1442
 * Get all view templates.
1443
 *
1444
 * Templates are special in-code views that are never active, but exist only
1445
 * to be cloned into real views as though they were templates.
1446
 */
1447
function views_get_all_templates() {
1448
  $templates = array();
1449
  $modules = views_module_include('views_template');
1450

    
1451
  foreach ($modules as $module => $info) {
1452
    $function = $module . '_views_templates';
1453
    if (function_exists($function)) {
1454
      $new = $function();
1455
      if ($new && is_array($new)) {
1456
        $templates = array_merge($new, $templates);
1457
      }
1458
    }
1459
  }
1460

    
1461
  return $templates;
1462
}
1463

    
1464
/**
1465
 * Create an empty view to work with.
1466
 *
1467
 * @return view
1468
 *   A fully formed, empty $view object. This object must be populated before
1469
 *   it can be successfully saved.
1470
 */
1471
function views_new_view() {
1472
  views_include('view');
1473
  $view = new view();
1474
  $view->vid = 'new';
1475
  $view->add_display('default');
1476

    
1477
  return $view;
1478
}
1479

    
1480
/**
1481
 * Get applicable views.
1482
 *
1483
 * Return a list of all views and display IDs that have a particular setting in
1484
 * their display's plugin settings.
1485
 *
1486
 * @return array
1487
 *   An array with the following structure.
1488
 *   array(
1489
 *     array($view, $display_id),
1490
 *     array($view, $display_id),
1491
 *   );
1492
 */
1493
function views_get_applicable_views($type) {
1494
  // @todo Use a smarter flagging system so that we don't have to
1495
  // load every view for this.
1496
  $result = array();
1497
  $views = views_get_all_views();
1498

    
1499
  foreach ($views as $view) {
1500
    // Skip disabled views.
1501
    if (!empty($view->disabled)) {
1502
      continue;
1503
    }
1504

    
1505
    if (empty($view->display)) {
1506
      // Skip this view as it is broken.
1507
      vsm(t("Skipping broken view @view", array('@view' => $view->name)));
1508
      continue;
1509
    }
1510

    
1511
    // Loop on array keys because something seems to muck with $view->display
1512
    // a bit in PHP4.
1513
    foreach (array_keys($view->display) as $id) {
1514
      $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
1515
      if (!empty($plugin[$type])) {
1516
        // This view uses hook menu. Clone it so that different handlers
1517
        // don't trip over each other, and add it to the list.
1518
        $v = $view->clone_view();
1519
        if ($v->set_display($id) && $v->display_handler->get_option('enabled')) {
1520
          $result[] = array($v, $id);
1521
        }
1522
        // In PHP 4.4.7 and presumably earlier, if we do not unset $v
1523
        // here, we will find that it actually overwrites references
1524
        // possibly due to shallow copying issues.
1525
        unset($v);
1526
      }
1527
    }
1528
  }
1529
  return $result;
1530
}
1531

    
1532
/**
1533
 * Return an array of all views as fully loaded $view objects.
1534
 *
1535
 * @param bool $reset
1536
 *   If TRUE, reset the static cache forcing views to be reloaded.
1537
 */
1538
function views_get_all_views($reset = FALSE) {
1539
  ctools_include('export');
1540
  return ctools_export_crud_load_all('views_view', $reset);
1541
}
1542

    
1543
/**
1544
 * Returns an array of all enabled views, as fully loaded $view objects.
1545
 */
1546
function views_get_enabled_views() {
1547
  $views = views_get_all_views();
1548
  return array_filter($views, 'views_view_is_enabled');
1549
}
1550

    
1551
/**
1552
 * Returns an array of all disabled views, as fully loaded $view objects.
1553
 */
1554
function views_get_disabled_views() {
1555
  $views = views_get_all_views();
1556
  return array_filter($views, 'views_view_is_disabled');
1557
}
1558

    
1559
/**
1560
 * Get options array.
1561
 *
1562
 * Return an array of view as options array, that can be used by select,
1563
 * checkboxes and radios as #options.
1564
 *
1565
 * @param bool $views_only
1566
 *   If TRUE, only return views, not displays.
1567
 * @param string $filter
1568
 *   Filters the views on status. Can either be 'all' (default), 'enabled' or
1569
 *   'disabled'.
1570
 * @param mixed $exclude_view
1571
 *   View or current display to exclude
1572
 *   either a
1573
 *   - views object (containing name and current_display)
1574
 *   - views name as string:  e.g. my_view
1575
 *   - views name and display id (separated by ':'): e.g. my_view:default.
1576
 * @param bool $optgroup
1577
 *   If TRUE, returns an array with optgroups for each view (will be ignored for
1578
 *   $views_only = TRUE). Can be used by select.
1579
 * @param bool $sort
1580
 *   If TRUE, the list of views is sorted ascending.
1581
 *
1582
 * @return array
1583
 *   An associative array for use in select.
1584
 *   - key: view name and display id separated by ':', or the view name only
1585
 */
1586
function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE, $sort = FALSE) {
1587

    
1588
  // Filter the big views array.
1589
  switch ($filter) {
1590
    case 'all':
1591
    case 'disabled':
1592
    case 'enabled':
1593
      $func = "views_get_{$filter}_views";
1594
      $views = $func();
1595
      break;
1596

    
1597
    default:
1598
      return array();
1599
  }
1600

    
1601
  // Prepare exclude view strings for comparison.
1602
  if (empty($exclude_view)) {
1603
    $exclude_view_name = '';
1604
    $exclude_view_display = '';
1605
  }
1606
  elseif (is_object($exclude_view)) {
1607
    $exclude_view_name = $exclude_view->name;
1608
    $exclude_view_display = $exclude_view->current_display;
1609
  }
1610
  else {
1611
    list($exclude_view_name, $exclude_view_display) = explode(':', $exclude_view);
1612
  }
1613

    
1614
  $options = array();
1615
  foreach ($views as $view) {
1616
    // Return only views.
1617
    if ($views_only && $view->name != $exclude_view_name) {
1618
      $options[$view->name] = $view->get_human_name();
1619
    }
1620
    // Return views with display ids.
1621
    else {
1622
      foreach ($view->display as $display_id => $display) {
1623
        if (!($view->name == $exclude_view_name && $display_id == $exclude_view_display)) {
1624
          if ($optgroup) {
1625
            $options[$view->name][$view->name . ':' . $display->id] = t('@view : @display', array('@view' => $view->name, '@display' => $display->id));
1626
          }
1627
          else {
1628
            $options[$view->name . ':' . $display->id] = t('View: @view - Display: @display', array('@view' => $view->name, '@display' => $display->id));
1629
          }
1630
        }
1631
      }
1632
    }
1633
  }
1634
  if ($sort) {
1635
    ksort($options);
1636
  }
1637
  return $options;
1638
}
1639

    
1640
/**
1641
 * Returns TRUE if a view is enabled, FALSE otherwise.
1642
 */
1643
function views_view_is_enabled($view) {
1644
  return empty($view->disabled);
1645
}
1646

    
1647
/**
1648
 * Returns TRUE if a view is disabled, FALSE otherwise.
1649
 */
1650
function views_view_is_disabled($view) {
1651
  return !empty($view->disabled);
1652
}
1653

    
1654
/**
1655
 * Get a view from the database or from default views.
1656
 *
1657
 * This function is just a static wrapper around views::load(). This function
1658
 * isn't called 'views_load()' primarily because it might get a view
1659
 * from the default views which aren't technically loaded from the database.
1660
 *
1661
 * @param string $name
1662
 *   The name of the view.
1663
 * @param bool $reset
1664
 *   If TRUE, reset this entry in the load cache.
1665
 *
1666
 * @return view
1667
 *   A reference to the $view object. Use $reset if you're sure you want
1668
 *   a fresh one.
1669
 */
1670
function views_get_view($name, $reset = FALSE) {
1671
  if ($reset) {
1672
    $cache = &drupal_static('ctools_export_load_object');
1673
    if (isset($cache['views_view'][$name])) {
1674
      unset($cache['views_view'][$name]);
1675
    }
1676
  }
1677

    
1678
  ctools_include('export');
1679
  $view = ctools_export_crud_load('views_view', $name);
1680
  if ($view) {
1681
    $view->update();
1682
    return $view->clone_view();
1683
  }
1684
}
1685

    
1686
/**
1687
 * Find the real location of a table.
1688
 *
1689
 * If a table has moved, find the new name of the table so that we can
1690
 * change its name directly in options where necessary.
1691
 */
1692
function views_move_table($table) {
1693
  $data = views_fetch_data($table, FALSE);
1694
  if (isset($data['moved to'])) {
1695
    $table = $data['moved to'];
1696
  }
1697

    
1698
  return $table;
1699
}
1700

    
1701
/**
1702
 * Export callback to load the view subrecords, which are the displays.
1703
 */
1704
function views_load_display_records(&$views) {
1705
  // Get vids from the views.
1706
  $names = array();
1707
  foreach ($views as $view) {
1708
    if (empty($view->display)) {
1709
      $names[$view->vid] = $view->name;
1710
    }
1711
  }
1712

    
1713
  if (empty($names)) {
1714
    return;
1715
  }
1716

    
1717
  foreach (view::db_objects() as $key) {
1718
    $object_name = "views_$key";
1719
    $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
1720
      array(':vids' => array_keys($names)));
1721

    
1722
    foreach ($result as $data) {
1723
      $object = new $object_name(FALSE);
1724
      $object->load_row($data);
1725

    
1726
      // Because it can get complicated with this much indirection,
1727
      // make a shortcut reference.
1728
      $location = &$views[$names[$object->vid]]->$key;
1729

    
1730
      // If we have a basic id field, load the item onto the view based on
1731
      // this ID, otherwise push it on.
1732
      if (!empty($object->id)) {
1733
        $location[$object->id] = $object;
1734
      }
1735
      else {
1736
        $location[] = $object;
1737
      }
1738
    }
1739
  }
1740
}
1741

    
1742
/**
1743
 * Export CRUD callback to save a view.
1744
 */
1745
function views_save_view(&$view) {
1746
  return $view->save();
1747
}
1748

    
1749
/**
1750
 * Export CRUD callback to delete a view.
1751
 */
1752
function views_delete_view(&$view) {
1753
  return $view->delete(TRUE);
1754
}
1755

    
1756
/**
1757
 * Export CRUD callback to export a view.
1758
 */
1759
function views_export_view(&$view, $indent = '') {
1760
  return $view->export($indent);
1761
}
1762

    
1763
/**
1764
 * Export callback to change view status.
1765
 */
1766
function views_export_status($view, $status) {
1767
  ctools_export_set_object_status($view, $status);
1768
  views_invalidate_cache();
1769
}
1770

    
1771
/**
1772
 * Provide debug output for Views.
1773
 *
1774
 * This relies on devel.module
1775
 * or on the debug() function if you use a simpletest.
1776
 *
1777
 * @param mixed $message
1778
 *   The message/variable which should be debugged.
1779
 *   This either could be
1780
 *     * an array/object which is converted to pretty output
1781
 *     * a translation source string which is used together with the parameter
1782
 *       placeholders.
1783
 * @param array $placeholders
1784
 *   The placeholders which are used for the translation source string.
1785
 */
1786
function views_debug($message, $placeholders = array()) {
1787
  if (!is_string($message)) {
1788
    $output = '<pre>' . var_export($message, TRUE) . '</pre>';
1789
  }
1790
  if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
1791
    $devel_region = variable_get('views_devel_region', 'footer');
1792
    if ($devel_region == 'watchdog') {
1793
      $output = $message;
1794
      watchdog('views_logging', $output, $placeholders);
1795
    }
1796
    elseif ($devel_region == 'drupal_debug') {
1797
      $output = empty($output) ? t($message, $placeholders) : $output;
1798
      dd($output);
1799
    }
1800
    else {
1801
      $output = empty($output) ? t($message, $placeholders) : $output;
1802
      dpm($output);
1803
    }
1804
  }
1805
  elseif (isset($GLOBALS['drupal_test_info'])) {
1806
    $output = empty($output) ? t($message, $placeholders) : $output;
1807
    debug($output);
1808
  }
1809
}
1810

    
1811
/**
1812
 * Shortcut to views_debug().
1813
 */
1814
function vpr($message, $placeholders = array()) {
1815
  views_debug($message, $placeholders);
1816
}
1817

    
1818
/**
1819
 * Debug messages.
1820
 */
1821
function vsm($message) {
1822
  if (module_exists('devel')) {
1823
    dpm($message);
1824
  }
1825
}
1826

    
1827
function views_trace() {
1828
  $message = '';
1829
  foreach (debug_backtrace() as $item) {
1830
    $traces = array('vsm_trace', 'vpr_trace', 'views_trace');
1831
    if (!empty($item['file']) && !in_array($item['function'], $traces)) {
1832
      $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
1833
    }
1834
  }
1835
  return $message;
1836
}
1837

    
1838
function vsm_trace() {
1839
  vsm(views_trace());
1840
}
1841

    
1842
function vpr_trace() {
1843
  dpr(views_trace());
1844
}
1845

    
1846
/**
1847
 * Determine whether the view has form elements.
1848
 *
1849
 * Returns TRUE if the passed-in view contains handlers with views form
1850
 * implementations, FALSE otherwise.
1851
 */
1852
function views_view_has_form_elements($view) {
1853
  foreach ($view->field as $field) {
1854
    if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
1855
      return TRUE;
1856
    }
1857
  }
1858
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1859
  $empty = empty($view->result);
1860
  foreach ($area_handlers as $area) {
1861
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1862
      return TRUE;
1863
    }
1864
  }
1865
  return FALSE;
1866
}
1867

    
1868
/**
1869
 * This is the entry function. Just gets the form for the current step.
1870
 *
1871
 * The form is always assumed to be multistep, even if it has only one
1872
 * step (the default 'views_form_views_form' step). That way it is actually
1873
 * possible for modules to have a multistep form if they need to.
1874
 */
1875
function views_form($form, &$form_state, $view, $output) {
1876
  $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';
1877
  // Cache the built form to prevent it from being rebuilt prior to validation
1878
  // and submission, which could lead to data being processed incorrectly,
1879
  // because the views rows (and thus, the form elements as well) have changed
1880
  // in the meantime.
1881
  $form_state['cache'] = TRUE;
1882

    
1883
  $form = array();
1884
  $query = drupal_get_query_parameters($_GET, array('q'));
1885
  $form['#action'] = url($view->get_url(), array('query' => $query));
1886
  // Tell the preprocessor whether it should hide the header, footer, pager...
1887
  $form['show_view_elements'] = array(
1888
    '#type' => 'value',
1889
    '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
1890
  );
1891

    
1892
  $form = $form_state['step']($form, $form_state, $view, $output);
1893
  return $form;
1894
}
1895

    
1896
/**
1897
 * Callback for the main step of a Views form.
1898
 *
1899
 * Invoked by views_form().
1900
 */
1901
function views_form_views_form($form, &$form_state, $view, $output) {
1902
  $form['#prefix'] = '<div class="views-form">';
1903
  $form['#suffix'] = '</div>';
1904
  $form['#theme'] = 'views_form_views_form';
1905
  $form['#validate'][] = 'views_form_views_form_validate';
1906
  $form['#submit'][] = 'views_form_views_form_submit';
1907

    
1908
  // Add the output markup to the form array so that it's included when the form
1909
  // array is passed to the theme function.
1910
  $form['output'] = array(
1911
    '#type' => 'markup',
1912
    '#markup' => $output,
1913
    // This way any additional form elements will go before the view
1914
    // (below the exposed widgets).
1915
    '#weight' => 50,
1916
  );
1917

    
1918
  $substitutions = array();
1919
  foreach ($view->field as $field_name => $field) {
1920
    $form_element_name = $field_name;
1921
    if (method_exists($field, 'form_element_name')) {
1922
      $form_element_name = $field->form_element_name();
1923
    }
1924
    $method_form_element_row_id_exists = FALSE;
1925
    if (method_exists($field, 'form_element_row_id')) {
1926
      $method_form_element_row_id_exists = TRUE;
1927
    }
1928

    
1929
    // If the field provides a views form, allow it to modify the $form array.
1930
    $has_form = FALSE;
1931
    if (property_exists($field, 'views_form_callback')) {
1932
      $callback = $field->views_form_callback;
1933
      $callback($view, $field, $form, $form_state);
1934
      $has_form = TRUE;
1935
    }
1936
    elseif (method_exists($field, 'views_form')) {
1937
      $field->views_form($form, $form_state);
1938
      $has_form = TRUE;
1939
    }
1940

    
1941
    // Build the substitutions array for use in the theme function.
1942
    if ($has_form) {
1943
      foreach ($view->result as $row_id => $row) {
1944
        if ($method_form_element_row_id_exists) {
1945
          $form_element_row_id = $field->form_element_row_id($row_id);
1946
        }
1947
        else {
1948
          $form_element_row_id = $row_id;
1949
        }
1950

    
1951
        $substitutions[] = array(
1952
          'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
1953
          'field_name' => $form_element_name,
1954
          'row_id' => $form_element_row_id,
1955
        );
1956
      }
1957
    }
1958
  }
1959

    
1960
  // Give the area handlers a chance to extend the form.
1961
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1962
  $empty = empty($view->result);
1963
  foreach ($area_handlers as $area) {
1964
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1965
      $area->views_form($form, $form_state);
1966
    }
1967
  }
1968

    
1969
  $form['#substitutions'] = array(
1970
    '#type' => 'value',
1971
    '#value' => $substitutions,
1972
  );
1973
  $form['actions'] = array(
1974
    '#type' => 'container',
1975
    '#attributes' => array('class' => array('form-actions')),
1976
    '#weight' => 100,
1977
  );
1978
  $form['actions']['submit'] = array(
1979
    '#type' => 'submit',
1980
    '#value' => t('Save'),
1981
  );
1982

    
1983
  return $form;
1984
}
1985

    
1986
/**
1987
 * Validate handler for the first step of the views form.
1988
 *
1989
 * Calls any existing views_form_validate functions located
1990
 * on the views fields.
1991
 */
1992
function views_form_views_form_validate($form, &$form_state) {
1993
  $view = $form_state['build_info']['args'][0];
1994

    
1995
  // Call the validation method on every field handler that has it.
1996
  foreach ($view->field as $field_name => $field) {
1997
    if (method_exists($field, 'views_form_validate')) {
1998
      $field->views_form_validate($form, $form_state);
1999
    }
2000
  }
2001

    
2002
  // Call the validate method on every area handler that has it.
2003
  foreach (array('header', 'footer') as $area) {
2004
    foreach ($view->{$area} as $area_name => $area_handler) {
2005
      if (method_exists($area_handler, 'views_form_validate')) {
2006
        $area_handler->views_form_validate($form, $form_state);
2007
      }
2008
    }
2009
  }
2010
}
2011

    
2012
/**
2013
 * Submit handler for the first step of the views form.
2014
 *
2015
 * Calls any existing views_form_submit functions located
2016
 * on the views fields.
2017
 */
2018
function views_form_views_form_submit($form, &$form_state) {
2019
  $view = $form_state['build_info']['args'][0];
2020

    
2021
  // Call the submit method on every field handler that has it.
2022
  foreach ($view->field as $field_name => $field) {
2023
    if (method_exists($field, 'views_form_submit')) {
2024
      $field->views_form_submit($form, $form_state);
2025
    }
2026
  }
2027

    
2028
  // Call the submit method on every area handler that has it.
2029
  foreach (array('header', 'footer') as $area) {
2030
    foreach ($view->{$area} as $area_name => $area_handler) {
2031
      if (method_exists($area_handler, 'views_form_submit')) {
2032
        $area_handler->views_form_submit($form, $form_state);
2033
      }
2034
    }
2035
  }
2036
}
2037

    
2038
/**
2039
 * Form builder for the exposed widgets form.
2040
 *
2041
 * Be sure that $view and $display are references.
2042
 */
2043
function views_exposed_form($form, &$form_state) {
2044
  // Don't show the form when batch operations are in progress.
2045
  if ($batch = batch_get() && isset($batch['current_set'])) {
2046
    return array(
2047
      // Set the theme callback to be nothing to avoid errors in
2048
      // template_preprocess_views_exposed_form().
2049
      '#theme' => '',
2050
    );
2051
  }
2052

    
2053
  // Make sure that we validate because this form might be submitted
2054
  // multiple times per page.
2055
  $form_state['must_validate'] = TRUE;
2056
  $view = &$form_state['view'];
2057
  $display = &$form_state['display'];
2058

    
2059
  $form_state['input'] = $view->get_exposed_input();
2060

    
2061
  // Let form plugins know this is for exposed widgets.
2062
  $form_state['exposed'] = TRUE;
2063
  // Check if the form was already created.
2064
  if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
2065
    return $cache;
2066
  }
2067

    
2068
  $form['#info'] = array();
2069

    
2070
  if (!variable_get('clean_url', FALSE)) {
2071
    $form['q'] = array(
2072
      '#type' => 'hidden',
2073
      '#value' => $view->get_url(),
2074
    );
2075
  }
2076

    
2077
  // Go through each handler and let it generate its exposed widget.
2078
  foreach ($view->display_handler->handlers as $type => $value) {
2079
    foreach ($view->$type as $id => $handler) {
2080
      if ($handler->can_expose() && $handler->is_exposed()) {
2081
        // Grouped exposed filters have their own forms.
2082
        // Instead of render the standard exposed form, a new Select or
2083
        // Radio form field is rendered with the available groups.
2084
        // When an user choose an option the selected value is split
2085
        // into the operator and value that the item represents.
2086
        if ($handler->is_a_group()) {
2087
          $handler->group_form($form, $form_state);
2088
          $id = $handler->options['group_info']['identifier'];
2089
        }
2090
        else {
2091
          $handler->exposed_form($form, $form_state);
2092
        }
2093
        if ($info = $handler->exposed_info()) {
2094
          $form['#info']["$type-$id"] = $info;
2095
        }
2096
      }
2097
    }
2098
  }
2099

    
2100
  // Form submit, #name is an empty string to prevent showing up in $_GET.
2101
  $form['submit'] = array(
2102
    '#name' => '',
2103
    '#type' => 'submit',
2104
    '#value' => t('Apply'),
2105
    '#id' => drupal_html_id('edit-submit-' . $view->name),
2106
  );
2107

    
2108
  $form['#action'] = url($view->display_handler->get_url());
2109
  $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
2110
  $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
2111

    
2112
  // If using AJAX, we need the form plugin.
2113
  if ($view->use_ajax) {
2114
    drupal_add_library('system', 'jquery.form');
2115
  }
2116
  ctools_include('dependent');
2117

    
2118
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2119
  $exposed_form_plugin->exposed_form_alter($form, $form_state);
2120

    
2121
  // Save the form.
2122
  views_exposed_form_cache($view->name, $view->current_display, $form);
2123

    
2124
  return $form;
2125
}
2126

    
2127
/**
2128
 * Implements hook_form_alter() for views_exposed_form().
2129
 *
2130
 * Since the exposed form is a GET form, we don't want it to send a wide
2131
 * variety of information.
2132
 */
2133
function views_form_views_exposed_form_alter(&$form, &$form_state) {
2134
  $form['form_build_id']['#access'] = FALSE;
2135
  $form['form_token']['#access'] = FALSE;
2136
  $form['form_id']['#access'] = FALSE;
2137
}
2138

    
2139
/**
2140
 * Validate handler for exposed filters.
2141
 */
2142
function views_exposed_form_validate(&$form, &$form_state) {
2143
  foreach (array('field', 'filter') as $type) {
2144
    $handlers = &$form_state['view']->$type;
2145
    foreach ($handlers as $key => $handler) {
2146
      $handlers[$key]->exposed_validate($form, $form_state);
2147
    }
2148
  }
2149
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2150
  $exposed_form_plugin->exposed_form_validate($form, $form_state);
2151
}
2152

    
2153
/**
2154
 * Submit handler for exposed filters.
2155
 */
2156
function views_exposed_form_submit(&$form, &$form_state) {
2157
  foreach (array('field', 'filter') as $type) {
2158
    $handlers = &$form_state['view']->$type;
2159
    foreach ($handlers as $key => $info) {
2160
      $handlers[$key]->exposed_submit($form, $form_state);
2161
    }
2162
  }
2163
  $form_state['view']->exposed_data = $form_state['values'];
2164
  $form_state['view']->exposed_raw_input = array();
2165

    
2166
  $exclude = array(
2167
    'q',
2168
    'submit',
2169
    'form_build_id',
2170
    'form_id',
2171
    'form_token',
2172
    'exposed_form_plugin',
2173
    '',
2174
    'reset',
2175
  );
2176
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2177
  $exposed_form_plugin->exposed_form_submit($form, $form_state, $exclude);
2178

    
2179
  foreach ($form_state['values'] as $key => $value) {
2180
    if (!in_array($key, $exclude)) {
2181
      $form_state['view']->exposed_raw_input[$key] = $value;
2182
    }
2183
  }
2184
}
2185

    
2186
/**
2187
 * Save the Views exposed form for later use.
2188
 *
2189
 * @param string $views_name
2190
 *   The views name.
2191
 * @param string $display_name
2192
 *   The current view display name.
2193
 * @param array $form_output
2194
 *   An optional form structure. Only needed when inserting the value.
2195
 *
2196
 * @return array|bool
2197
 *   Array. The form structure, if any. Otherwise, return FALSE.
2198
 */
2199
function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
2200
  // When running tests for exposed filters, this cache should
2201
  // be cleared between each test.
2202
  $views_exposed = &drupal_static(__FUNCTION__);
2203

    
2204
  // Save the form output.
2205
  if (!empty($form_output)) {
2206
    $views_exposed[$views_name][$display_name] = $form_output;
2207
  }
2208

    
2209
  // Return the form output, if any.
2210
  return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
2211
}
2212

    
2213
/**
2214
 * Build a list of theme function names for use most everywhere.
2215
 */
2216
function views_theme_functions($hook, $view, $display = NULL) {
2217
  require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'views') . "/theme/theme.inc";
2218
  return _views_theme_functions($hook, $view, $display);
2219
}
2220

    
2221
/**
2222
 * Substitute current time; this works with cached queries.
2223
 */
2224
function views_views_query_substitutions($view) {
2225
  global $language_content;
2226
  return array(
2227
    '***CURRENT_VERSION***' => VERSION,
2228
    '***CURRENT_TIME***' => REQUEST_TIME,
2229
    '***CURRENT_LANGUAGE***' => $language_content->language,
2230
    '***DEFAULT_LANGUAGE***' => language_default('language'),
2231
  );
2232
}
2233

    
2234
/**
2235
 * Implements hook_query_TAG_alter().
2236
 *
2237
 * This is the hook_query_alter() for queries tagged by Views and is used to
2238
 * add in substitutions from hook_views_query_substitutions().
2239
 */
2240
function views_query_views_alter(QueryAlterableInterface $query) {
2241
  $substitutions = $query->getMetaData('views_substitutions');
2242
  $tables =& $query->getTables();
2243
  $where =& $query->conditions();
2244

    
2245
  // Replaces substitions in tables.
2246
  foreach ($tables as $table_name => $table_metadata) {
2247
    foreach ($table_metadata['arguments'] as $replacement_key => $value) {
2248
      if (isset($substitutions[$value])) {
2249
        $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
2250
      }
2251
    }
2252
  }
2253

    
2254
  // Replaces substitions in filter criterias.
2255
  _views_query_tag_alter_condition($query, $where, $substitutions);
2256
}
2257

    
2258
/**
2259
 * Replaces the substitutions recursive foreach condition.
2260
 */
2261
function _views_query_tag_alter_condition(QueryAlterableInterface $query, &$conditions, $substitutions) {
2262
  foreach ($conditions as $condition_id => &$condition) {
2263
    if (is_numeric($condition_id)) {
2264
      if (is_string($condition['field'])) {
2265
        $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
2266
      }
2267
      elseif (is_object($condition['field'])) {
2268
        $sub_conditions =& $condition['field']->conditions();
2269
        _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
2270
      }
2271
      // $condition['value'] is a subquery so alter the subquery recursive.
2272
      // Therefore take sure to get the metadata of the main query.
2273
      if (is_object($condition['value'])) {
2274
        $subquery = $condition['value'];
2275
        $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
2276
        views_query_views_alter($condition['value']);
2277
      }
2278
      elseif (isset($condition['value'])) {
2279
        $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
2280
      }
2281
    }
2282
  }
2283
}
2284

    
2285
/**
2286
 * Embed a view using a PHP snippet.
2287
 *
2288
 * This function is meant to be called from PHP snippets, should one wish to
2289
 * embed a view in a node or something. It's meant to provide the simplest
2290
 * solution and doesn't really offer a lot of options, but breaking the function
2291
 * apart is pretty easy, and this provides a worthwhile guide to doing so.
2292
 *
2293
 * Note that this function does NOT display the title of the view. If you want
2294
 * to do that, you will need to do what this function does manually, by
2295
 * loading the view, getting the preview and then getting $view->get_title().
2296
 *
2297
 * @param string $name
2298
 *   The name of the view to embed.
2299
 * @param string $display_id
2300
 *   The display id to embed. If unsure, use 'default', as it will always be
2301
 *   valid. But things like 'page' or 'block' should work here.
2302
 * @param ...
2303
 *   Any additional parameters will be passed as arguments.
2304
 */
2305
function views_embed_view($name, $display_id = 'default') {
2306
  $args = func_get_args();
2307
  // Remove $name.
2308
  array_shift($args);
2309
  if (count($args)) {
2310
    // Remove $display_id.
2311
    array_shift($args);
2312
  }
2313

    
2314
  $view = views_get_view($name);
2315
  if (!$view || !$view->access($display_id)) {
2316
    return;
2317
  }
2318

    
2319
  return $view->preview($display_id, $args);
2320
}
2321

    
2322
/**
2323
 * Get the result of a view.
2324
 *
2325
 * @param string $name
2326
 *   The name of the view to retrieve the data from.
2327
 * @param string $display_id
2328
 *   The display id. On the edit page for the view in question, you'll find
2329
 *   a list of displays at the left side of the control area. "Master"
2330
 *   will be at the top of that list. Hover your cursor over the name of the
2331
 *   display you want to use. An URL will appear in the status bar of your
2332
 *   browser. This is usually at the bottom of the window, in the chrome.
2333
 *   Everything after #views-tab- is the display ID, e.g. page_1.
2334
 * @param ...
2335
 *   Any additional parameters will be passed as arguments.
2336
 *
2337
 * @return array
2338
 *   An array containing an object for each view item.
2339
 */
2340
function views_get_view_result($name, $display_id = NULL) {
2341
  $args = func_get_args();
2342
  // Remove $name.
2343
  array_shift($args);
2344
  if (count($args)) {
2345
    // Remove $display_id.
2346
    array_shift($args);
2347
  }
2348

    
2349
  $view = views_get_view($name);
2350
  if (is_object($view)) {
2351
    if (is_array($args)) {
2352
      $view->set_arguments($args);
2353
    }
2354
    if (is_string($display_id)) {
2355
      $view->set_display($display_id);
2356
    }
2357
    else {
2358
      $view->init_display();
2359
    }
2360
    $view->pre_execute();
2361
    $view->execute();
2362
    return $view->result;
2363
  }
2364
  else {
2365
    return array();
2366
  }
2367
}
2368

    
2369
/**
2370
 * Export a field.
2371
 */
2372
function views_var_export($var, $prefix = '', $init = TRUE) {
2373
  if (is_array($var)) {
2374
    if (empty($var)) {
2375
      $output = 'array()';
2376
    }
2377
    else {
2378
      $output = "array(\n";
2379
      foreach ($var as $key => $value) {
2380
        $output .= "  " . views_var_export($key, '', FALSE) . " => " . views_var_export($value, '  ', FALSE) . ",\n";
2381
      }
2382
      $output .= ')';
2383
    }
2384
  }
2385
  elseif (is_bool($var)) {
2386
    $output = $var ? 'TRUE' : 'FALSE';
2387
  }
2388
  elseif (is_string($var) && strpos($var, "\n") !== FALSE) {
2389
    // Replace line breaks in strings with a token for replacement
2390
    // at the very end. This protects multi-line strings from
2391
    // unintentional indentation.
2392
    $var = str_replace("\n", "***BREAK***", $var);
2393
    $output = var_export($var, TRUE);
2394
  }
2395
  else {
2396
    $output = var_export($var, TRUE);
2397
  }
2398

    
2399
  if ($prefix) {
2400
    $output = str_replace("\n", "\n$prefix", $output);
2401
  }
2402

    
2403
  if ($init) {
2404
    $output = str_replace("***BREAK***", "\n", $output);
2405
  }
2406

    
2407
  return $output;
2408
}
2409

    
2410
/**
2411
 * Prepare a string for use as a valid CSS identifier.
2412
 *
2413
 * This function is similar to a core version but with more sane filter values.
2414
 * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
2415
 * CSS identifiers (including element names, classes, and IDs in selectors).
2416
 *
2417
 * @param string $identifier
2418
 *   The identifier to clean.
2419
 * @param array $filter
2420
 *   An array of string replacements to use on the identifier.
2421
 *
2422
 * @return string
2423
 *   The cleaned identifier.
2424
 *
2425
 * @see drupal_clean_css_identifier()
2426
 */
2427
function views_clean_css_identifier($identifier, $filter = array(
2428
  ' ' => '-',
2429
  '/' => '-',
2430
  '[' => '-',
2431
  ']' => '',
2432
)) {
2433
  // By default, we filter using Drupal's coding standards.
2434
  $identifier = strtr($identifier, $filter);
2435

    
2436
  // Valid characters in a CSS identifier are:
2437
  // - the hyphen (U+002D)
2438
  // - a-z (U+0030 - U+0039)
2439
  // - A-Z (U+0041 - U+005A)
2440
  // - the underscore (U+005F)
2441
  // - 0-9 (U+0061 - U+007A)
2442
  // - ISO 10646 characters U+00A1 and higher
2443
  // We strip out any character not in the above list.
2444
  $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);
2445

    
2446
  return $identifier;
2447
}
2448

    
2449
/**
2450
 * Implements hook_views_exportables().
2451
 */
2452
function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
2453
  $all_views = views_get_all_views();
2454
  if ($op == 'list') {
2455

    
2456
    foreach ($all_views as $name => $view) {
2457
      // In list, $views is a list of tags.
2458
      if (empty($views) || in_array($view->tag, $views)) {
2459
        $return[$name] = array(
2460
          'name' => check_plain($name),
2461
          'desc' => check_plain($view->description),
2462
          'tag' => check_plain($view->tag),
2463
        );
2464
      }
2465
    }
2466
    return $return;
2467
  }
2468

    
2469
  if ($op == 'export') {
2470
    $code = "/**\n";
2471
    $code .= " * Implement hook_views_default_views().\n";
2472
    $code .= " */\n";
2473
    $code .= "function " . $name . "_views_default_views() {\n";
2474
    foreach ($views as $view => $truth) {
2475
      $code .= "  /*\n";
2476
      $code .= "   * View " . var_export($all_views[$view]->name, TRUE) . "\n";
2477
      $code .= "   */\n";
2478
      $code .= $all_views[$view]->export('  ');
2479
      $code .= '  $views[$view->name] = $view;' . "\n\n";
2480
    }
2481
    $code .= "  return \$views;\n";
2482
    $code .= "}\n";
2483

    
2484
    return $code;
2485
  }
2486
}
2487

    
2488
/**
2489
 * Process callback to see if we need to check_plain() the options.
2490
 *
2491
 * Since FAPI is inconsistent, the #options are sanitized for you in all cases
2492
 * _except_ checkboxes. We have form elements that are sometimes 'select' and
2493
 * sometimes 'checkboxes', so we need decide late in the form rendering cycle
2494
 * if the options need to be sanitized before they're rendered. This callback
2495
 * inspects the type, and if it's still 'checkboxes', does the sanitation.
2496
 */
2497
function views_process_check_options($element, &$form_state) {
2498
  if ($element['#type'] == 'checkboxes' || $element['#type'] == 'checkbox') {
2499
    $element['#options'] = array_map('check_plain', $element['#options']);
2500
  }
2501
  return $element;
2502
}
2503

    
2504
/**
2505
 * Trim the field down to the specified length.
2506
 *
2507
 * @param array $alter
2508
 *   - max_length: Maximum length of the string, the rest gets truncated.
2509
 *   - word_boundary: Trim only on a word boundary.
2510
 *   - ellipsis: Show an ellipsis (...) at the end of the trimmed string.
2511
 *   - html: Take sure that the html is correct.
2512
 * @param string $value
2513
 *   The string which should be trimmed.
2514
 */
2515
function views_trim_text($alter, $value) {
2516
  if (drupal_strlen($value) > $alter['max_length']) {
2517
    $value = drupal_substr($value, 0, $alter['max_length']);
2518
    // @todo Replace this with cleanstring of CTools.
2519
    if (!empty($alter['word_boundary'])) {
2520
      $regex = "(.*)\b.+";
2521
      if (function_exists('mb_ereg')) {
2522
        mb_regex_encoding('UTF-8');
2523
        $found = mb_ereg($regex, $value, $matches);
2524
      }
2525
      else {
2526
        $found = preg_match("/$regex/us", $value, $matches);
2527
      }
2528
      if ($found) {
2529
        $value = $matches[1];
2530
      }
2531
    }
2532
    // Remove scraps of HTML entities from the end of a strings.
2533
    $value = rtrim(preg_replace('/(?:<(?!.+>)|&(?!.+;)).*$/us', '', $value));
2534

    
2535
    if (!empty($alter['ellipsis'])) {
2536
      $value .= t('...');
2537
    }
2538
  }
2539
  if (!empty($alter['html'])) {
2540
    $value = _filter_htmlcorrector($value);
2541
  }
2542

    
2543
  return $value;
2544
}
2545

    
2546
/**
2547
 * Adds one to each key of the array.
2548
 *
2549
 * For example array(0 => 'foo') would be array(1 => 'foo').
2550
 */
2551
function views_array_key_plus($array) {
2552
  $keys = array_keys($array);
2553
  rsort($keys);
2554
  foreach ($keys as $key) {
2555
    $array[$key + 1] = $array[$key];
2556
    unset($array[$key]);
2557
  }
2558
  asort($array);
2559
  return $array;
2560
}
2561

    
2562
/**
2563
 * Implements hook_ctools_plugin_api_hook_name().
2564
 *
2565
 * Report to CTools that we use hook_views_api instead of
2566
 * hook_ctools_plugin_api().
2567
 */
2568
function views_ctools_plugin_api_hook_name() {
2569
  return 'views_api';
2570
}
2571

    
2572
/**
2573
 * Implements hook_views_api().
2574
 *
2575
 * This one is used as the base to reduce errors when updating.
2576
 */
2577
function views_views_api() {
2578
  return array(
2579
    // In your modules do *not* use views_api_version()!!!
2580
    'api' => views_api_version(),
2581
    'path' => drupal_get_path('module', 'views') . '/modules',
2582
  );
2583
}
2584

    
2585
if (!function_exists('aggregator_views_api')) {
2586
  /**
2587
   * Provide Views integration for the Aggregator module.
2588
   */
2589
  function aggregator_views_api() {
2590
    return views_views_api();
2591
  }
2592
}
2593

    
2594
if (!function_exists('book_views_api')) {
2595
  /**
2596
   * Provide Views integration for the Book module.
2597
   */
2598
  function book_views_api() {
2599
    return views_views_api();
2600
  }
2601
}
2602

    
2603
if (!function_exists('comment_views_api')) {
2604
  /**
2605
   * Provide Views integration for the Comment module.
2606
   */
2607
  function comment_views_api() {
2608
    return views_views_api();
2609
  }
2610
}
2611

    
2612
if (!function_exists('field_views_api')) {
2613
  /**
2614
   * Provide Views integration for the Field module.
2615
   */
2616
  function field_views_api() {
2617
    return views_views_api();
2618
  }
2619
}
2620

    
2621
if (!function_exists('file_views_api')) {
2622
  /**
2623
   * Provide Views integration for the File module.
2624
   */
2625
  function file_views_api() {
2626
    return views_views_api();
2627
  }
2628
}
2629

    
2630
if (!function_exists('filter_views_api')) {
2631
  /**
2632
   * Provide Views integration for the Filter module.
2633
   */
2634
  function filter_views_api() {
2635
    return views_views_api();
2636
  }
2637
}
2638

    
2639
if (!function_exists('image_views_api')) {
2640
  /**
2641
   * Provide Views integration for the Image module.
2642
   */
2643
  function image_views_api() {
2644
    return views_views_api();
2645
  }
2646
}
2647

    
2648
if (!function_exists('locale_views_api')) {
2649
  /**
2650
   * Provide Views integration for the Locale module.
2651
   */
2652
  function locale_views_api() {
2653
    return views_views_api();
2654
  }
2655
}
2656

    
2657
if (!function_exists('node_views_api')) {
2658
  /**
2659
   * Provide Views integration for the Node module.
2660
   */
2661
  function node_views_api() {
2662
    return views_views_api();
2663
  }
2664
}
2665

    
2666
if (!function_exists('poll_views_api')) {
2667
  /**
2668
   * Provide Views integration for the Poll module.
2669
   */
2670
  function poll_views_api() {
2671
    return views_views_api();
2672
  }
2673
}
2674

    
2675
if (!function_exists('profile_views_api')) {
2676
  /**
2677
   * Provide Views integration for the Profile module.
2678
   */
2679
  function profile_views_api() {
2680
    return views_views_api();
2681
  }
2682
}
2683

    
2684
if (!function_exists('search_views_api')) {
2685
  /**
2686
   * Provide Views integration for the Search module.
2687
   */
2688
  function search_views_api() {
2689
    return views_views_api();
2690
  }
2691
}
2692

    
2693
if (!function_exists('statistics_views_api')) {
2694
  /**
2695
   * Provide Views integration for the Statistics module.
2696
   */
2697
  function statistics_views_api() {
2698
    return views_views_api();
2699
  }
2700
}
2701

    
2702
if (!function_exists('system_views_api')) {
2703
  /**
2704
   * Provide Views integration for the System module.
2705
   */
2706
  function system_views_api() {
2707
    return views_views_api();
2708
  }
2709
}
2710

    
2711
if (!function_exists('tracker_views_api')) {
2712
  /**
2713
   * Provide Views integration for the Tracker module.
2714
   */
2715
  function tracker_views_api() {
2716
    return views_views_api();
2717
  }
2718
}
2719

    
2720
if (!function_exists('taxonomy_views_api')) {
2721
  /**
2722
   * Provide Views integration for the Taxonomy module.
2723
   */
2724
  function taxonomy_views_api() {
2725
    return views_views_api();
2726
  }
2727
}
2728

    
2729
if (!function_exists('translation_views_api')) {
2730
  /**
2731
   * Provide Views integration for the Translation module.
2732
   */
2733
  function translation_views_api() {
2734
    return views_views_api();
2735
  }
2736
}
2737

    
2738
if (!function_exists('user_views_api')) {
2739
  /**
2740
   * Provide Views integration for the User module.
2741
   */
2742
  function user_views_api() {
2743
    return views_views_api();
2744
  }
2745
}
2746

    
2747
if (!function_exists('contact_views_api')) {
2748
  /**
2749
   * Provide Views integration for the Contact module.
2750
   */
2751
  function contact_views_api() {
2752
    return views_views_api();
2753
  }
2754
}