Projet

Général

Profil

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

root / drupal7 / sites / all / modules / views / views.module @ ba3b3627

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
 * @param string $cid
1017
 *   The cache identifier we want to clear. If no given, it will default to '*'
1018
 *   which will clear the entire cache_views bin.
1019
 */
1020
function views_invalidate_cache($cid = '*') {
1021
  // Clear the views cache.
1022
  cache_clear_all($cid, 'cache_views', TRUE);
1023

    
1024
  // Clear the page and block cache.
1025
  cache_clear_all();
1026

    
1027
  // Set the menu as needed to be rebuilt.
1028
  variable_set('menu_rebuild_needed', TRUE);
1029

    
1030
  // Allow modules to respond to the Views cache being cleared.
1031
  module_invoke_all('views_invalidate_cache', $cid);
1032
}
1033

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

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

    
1060
    if (!is_array($arg)) {
1061
      continue;
1062
    }
1063

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

    
1077
  return FALSE;
1078
}
1079

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

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

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

    
1131
  return $cache;
1132
}
1133

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

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

    
1163
  return $cache;
1164
}
1165

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

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

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

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

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

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

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

    
1261
  views_include('base');
1262
  views_include('handlers');
1263
  views_include('cache');
1264
  views_include('plugins');
1265
  views_module_include('views', $reset);
1266
  $finished = TRUE;
1267
}
1268

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

    
1288
  $data = views_fetch_data($table, FALSE);
1289
  $handler = NULL;
1290
  views_include('handlers');
1291

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

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

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

    
1328
    // Set up a default handler.
1329
    if (empty($data[$field][$key]['handler'])) {
1330
      $data[$field][$key]['handler'] = 'views_handler_' . $key;
1331
    }
1332

    
1333
    if ($override) {
1334
      $data[$field][$key]['override handler'] = $override;
1335
    }
1336

    
1337
    $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
1338
  }
1339

    
1340
  if ($handler) {
1341
    return $handler;
1342
  }
1343

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

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

    
1364
/**
1365
 * Fetch the plugin data from cache.
1366
 */
1367
function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
1368
  views_include('cache');
1369
  return _views_fetch_plugin_data($type, $plugin, $reset);
1370
}
1371

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

    
1389
  $plugins[$type] = array();
1390

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

    
1401
  if (!empty($plugins[$type])) {
1402
    asort($plugins[$type]);
1403
    return $plugins[$type];
1404
  }
1405

    
1406
  // Fall-through.
1407
  return array();
1408
}
1409

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

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

    
1442
  return $plugin;
1443
}
1444

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

    
1455
  foreach ($modules as $module => $info) {
1456
    $function = $module . '_views_templates';
1457
    if (function_exists($function)) {
1458
      $new = $function();
1459
      if ($new && is_array($new)) {
1460
        $templates = array_merge($new, $templates);
1461
      }
1462
    }
1463
  }
1464

    
1465
  return $templates;
1466
}
1467

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

    
1481
  return $view;
1482
}
1483

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

    
1503
  foreach ($views as $view) {
1504
    // Skip disabled views.
1505
    if (!empty($view->disabled)) {
1506
      continue;
1507
    }
1508

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

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

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

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

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

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

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

    
1601
    default:
1602
      return array();
1603
  }
1604

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

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

    
1644
/**
1645
 * Returns TRUE if a view is enabled, FALSE otherwise.
1646
 */
1647
function views_view_is_enabled($view) {
1648
  return empty($view->disabled);
1649
}
1650

    
1651
/**
1652
 * Returns TRUE if a view is disabled, FALSE otherwise.
1653
 */
1654
function views_view_is_disabled($view) {
1655
  return !empty($view->disabled);
1656
}
1657

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

    
1682
  ctools_include('export');
1683
  $view = ctools_export_crud_load('views_view', $name);
1684
  if ($view) {
1685
    $view->update();
1686
    return $view->clone_view();
1687
  }
1688
}
1689

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

    
1702
  return $table;
1703
}
1704

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

    
1717
  if (empty($names)) {
1718
    return;
1719
  }
1720

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

    
1726
    foreach ($result as $data) {
1727
      $object = new $object_name(FALSE);
1728
      $object->load_row($data);
1729

    
1730
      // Because it can get complicated with this much indirection,
1731
      // make a shortcut reference.
1732
      $location = &$views[$names[$object->vid]]->$key;
1733

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

    
1746
/**
1747
 * Export CRUD callback to save a view.
1748
 */
1749
function views_save_view(&$view) {
1750
  return $view->save();
1751
}
1752

    
1753
/**
1754
 * Export CRUD callback to delete a view.
1755
 */
1756
function views_delete_view(&$view) {
1757
  return $view->delete(TRUE);
1758
}
1759

    
1760
/**
1761
 * Export CRUD callback to export a view.
1762
 */
1763
function views_export_view(&$view, $indent = '') {
1764
  return $view->export($indent);
1765
}
1766

    
1767
/**
1768
 * Export callback to change view status.
1769
 */
1770
function views_export_status($view, $status) {
1771
  ctools_export_set_object_status($view, $status);
1772
  views_invalidate_cache();
1773
}
1774

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

    
1815
/**
1816
 * Shortcut to views_debug().
1817
 */
1818
function vpr($message, $placeholders = array()) {
1819
  views_debug($message, $placeholders);
1820
}
1821

    
1822
/**
1823
 * Debug messages.
1824
 */
1825
function vsm($message) {
1826
  if (module_exists('devel')) {
1827
    dpm($message);
1828
  }
1829
}
1830

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

    
1842
function vsm_trace() {
1843
  vsm(views_trace());
1844
}
1845

    
1846
function vpr_trace() {
1847
  dpr(views_trace());
1848
}
1849

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

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

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

    
1896
  $form = $form_state['step']($form, $form_state, $view, $output);
1897
  return $form;
1898
}
1899

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

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

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

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

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

    
1955
        $substitutions[] = array(
1956
          'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
1957
          'field_name' => $form_element_name,
1958
          'row_id' => $form_element_row_id,
1959
        );
1960
      }
1961
    }
1962
  }
1963

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

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

    
1987
  return $form;
1988
}
1989

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

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

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

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

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

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

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

    
2057
  // Make sure that we validate because this form might be submitted
2058
  // multiple times per page.
2059
  $form_state['must_validate'] = TRUE;
2060
  $view = &$form_state['view'];
2061
  $display = &$form_state['display'];
2062

    
2063
  $form_state['input'] = $view->get_exposed_input();
2064

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

    
2072
  $form['#info'] = array();
2073

    
2074
  if (!variable_get('clean_url', FALSE)) {
2075
    $form['q'] = array(
2076
      '#type' => 'hidden',
2077
      '#value' => $view->get_url(),
2078
    );
2079
  }
2080

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

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

    
2112
  $form['#action'] = url($view->display_handler->get_url());
2113
  $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
2114
  $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
2115

    
2116
  // If using AJAX, we need the form plugin.
2117
  if ($view->use_ajax) {
2118
    drupal_add_library('system', 'jquery.form');
2119
  }
2120
  ctools_include('dependent');
2121

    
2122
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2123
  $exposed_form_plugin->exposed_form_alter($form, $form_state);
2124

    
2125
  // Save the form.
2126
  views_exposed_form_cache($view->name, $view->current_display, $form);
2127

    
2128
  return $form;
2129
}
2130

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

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

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

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

    
2183
  foreach ($form_state['values'] as $key => $value) {
2184
    if (!in_array($key, $exclude)) {
2185
      $form_state['view']->exposed_raw_input[$key] = $value;
2186
    }
2187
  }
2188
}
2189

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

    
2208
  // Save the form output.
2209
  if (!empty($form_output)) {
2210
    $views_exposed[$views_name][$display_name] = $form_output;
2211
  }
2212

    
2213
  // Return the form output, if any.
2214
  return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
2215
}
2216

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

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

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

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

    
2258
  // Replaces substitions in filter criterias.
2259
  _views_query_tag_alter_condition($query, $where, $substitutions);
2260
}
2261

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

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

    
2318
  $view = views_get_view($name);
2319
  if (!$view || !$view->access($display_id)) {
2320
    return;
2321
  }
2322

    
2323
  return $view->preview($display_id, $args);
2324
}
2325

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

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

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

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

    
2407
  if ($init) {
2408
    $output = str_replace("***BREAK***", "\n", $output);
2409
  }
2410

    
2411
  return $output;
2412
}
2413

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

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

    
2450
  return $identifier;
2451
}
2452

    
2453
/**
2454
 * Implements hook_views_exportables().
2455
 */
2456
function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
2457
  $all_views = views_get_all_views();
2458
  if ($op == 'list') {
2459

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

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

    
2488
    return $code;
2489
  }
2490
}
2491

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

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

    
2539
    if (!empty($alter['ellipsis'])) {
2540
      $value .= t('...');
2541
    }
2542
  }
2543
  if (!empty($alter['html'])) {
2544
    $value = _filter_htmlcorrector($value);
2545
  }
2546

    
2547
  return $value;
2548
}
2549

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

    
2566
/**
2567
 * Implements hook_ctools_plugin_api_hook_name().
2568
 *
2569
 * Report to CTools that we use hook_views_api instead of
2570
 * hook_ctools_plugin_api().
2571
 */
2572
function views_ctools_plugin_api_hook_name() {
2573
  return 'views_api';
2574
}
2575

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

    
2589
if (!function_exists('aggregator_views_api')) {
2590
  /**
2591
   * Provide Views integration for the Aggregator module.
2592
   */
2593
  function aggregator_views_api() {
2594
    return views_views_api();
2595
  }
2596
}
2597

    
2598
if (!function_exists('book_views_api')) {
2599
  /**
2600
   * Provide Views integration for the Book module.
2601
   */
2602
  function book_views_api() {
2603
    return views_views_api();
2604
  }
2605
}
2606

    
2607
if (!function_exists('comment_views_api')) {
2608
  /**
2609
   * Provide Views integration for the Comment module.
2610
   */
2611
  function comment_views_api() {
2612
    return views_views_api();
2613
  }
2614
}
2615

    
2616
if (!function_exists('field_views_api')) {
2617
  /**
2618
   * Provide Views integration for the Field module.
2619
   */
2620
  function field_views_api() {
2621
    return views_views_api();
2622
  }
2623
}
2624

    
2625
if (!function_exists('file_views_api')) {
2626
  /**
2627
   * Provide Views integration for the File module.
2628
   */
2629
  function file_views_api() {
2630
    return views_views_api();
2631
  }
2632
}
2633

    
2634
if (!function_exists('filter_views_api')) {
2635
  /**
2636
   * Provide Views integration for the Filter module.
2637
   */
2638
  function filter_views_api() {
2639
    return views_views_api();
2640
  }
2641
}
2642

    
2643
if (!function_exists('image_views_api')) {
2644
  /**
2645
   * Provide Views integration for the Image module.
2646
   */
2647
  function image_views_api() {
2648
    return views_views_api();
2649
  }
2650
}
2651

    
2652
if (!function_exists('locale_views_api')) {
2653
  /**
2654
   * Provide Views integration for the Locale module.
2655
   */
2656
  function locale_views_api() {
2657
    return views_views_api();
2658
  }
2659
}
2660

    
2661
if (!function_exists('node_views_api')) {
2662
  /**
2663
   * Provide Views integration for the Node module.
2664
   */
2665
  function node_views_api() {
2666
    return views_views_api();
2667
  }
2668
}
2669

    
2670
if (!function_exists('poll_views_api')) {
2671
  /**
2672
   * Provide Views integration for the Poll module.
2673
   */
2674
  function poll_views_api() {
2675
    return views_views_api();
2676
  }
2677
}
2678

    
2679
if (!function_exists('profile_views_api')) {
2680
  /**
2681
   * Provide Views integration for the Profile module.
2682
   */
2683
  function profile_views_api() {
2684
    return views_views_api();
2685
  }
2686
}
2687

    
2688
if (!function_exists('search_views_api')) {
2689
  /**
2690
   * Provide Views integration for the Search module.
2691
   */
2692
  function search_views_api() {
2693
    return views_views_api();
2694
  }
2695
}
2696

    
2697
if (!function_exists('statistics_views_api')) {
2698
  /**
2699
   * Provide Views integration for the Statistics module.
2700
   */
2701
  function statistics_views_api() {
2702
    return views_views_api();
2703
  }
2704
}
2705

    
2706
if (!function_exists('system_views_api')) {
2707
  /**
2708
   * Provide Views integration for the System module.
2709
   */
2710
  function system_views_api() {
2711
    return views_views_api();
2712
  }
2713
}
2714

    
2715
if (!function_exists('tracker_views_api')) {
2716
  /**
2717
   * Provide Views integration for the Tracker module.
2718
   */
2719
  function tracker_views_api() {
2720
    return views_views_api();
2721
  }
2722
}
2723

    
2724
if (!function_exists('taxonomy_views_api')) {
2725
  /**
2726
   * Provide Views integration for the Taxonomy module.
2727
   */
2728
  function taxonomy_views_api() {
2729
    return views_views_api();
2730
  }
2731
}
2732

    
2733
if (!function_exists('translation_views_api')) {
2734
  /**
2735
   * Provide Views integration for the Translation module.
2736
   */
2737
  function translation_views_api() {
2738
    return views_views_api();
2739
  }
2740
}
2741

    
2742
if (!function_exists('user_views_api')) {
2743
  /**
2744
   * Provide Views integration for the User module.
2745
   */
2746
  function user_views_api() {
2747
    return views_views_api();
2748
  }
2749
}
2750

    
2751
if (!function_exists('contact_views_api')) {
2752
  /**
2753
   * Provide Views integration for the Contact module.
2754
   */
2755
  function contact_views_api() {
2756
    return views_views_api();
2757
  }
2758
}