Projet

Général

Profil

Paste
Télécharger (83,9 ko) Statistiques
| Branche: | Révision:

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

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
 * Implement hook_theme(). Register views theming functions.
85
 */
86
function views_theme($existing, $type, $theme, $path) {
87
  $path = drupal_get_path('module', 'views');
88
  ctools_include('theme', 'views', 'theme');
89

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

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

    
102
  $variables = array(
103
    // For displays, we pass in a dummy array as the first parameter, since
104
    // $view is an object but the core contextual_preprocess() function only
105
    // attaches contextual links when the primary theme argument is an array.
106
    'display' => array('view_array' => array(), 'view' => NULL),
107
    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
108
    'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
109
    'exposed_form' => array('view' => NULL, 'options' => NULL),
110
    'pager' => array(
111
      'view' => NULL, 'options' => NULL,
112
      'tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()
113
    ),
114
  );
115

    
116
  // Default view themes
117
  $hooks['views_view_field'] = $base + array(
118
    'pattern' => 'views_view_field__',
119
    'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
120
  );
121
  $hooks['views_view_grouping'] = $base + array(
122
    'pattern' => 'views_view_grouping__',
123
    'variables' => array('view' => NULL, 'grouping' => NULL, 'grouping_level' => NULL, 'rows' => NULL, 'title' => NULL),
124
  );
125

    
126
  $plugins = views_fetch_plugin_data();
127

    
128
  // Register theme functions for all style plugins
129
  foreach ($plugins as $type => $info) {
130
    foreach ($info as $plugin => $def) {
131
      if (isset($def['theme']) && (!isset($def['register theme']) || !empty($def['register theme']))) {
132
        $hooks[$def['theme']] = array(
133
          'pattern' => $def['theme'] . '__',
134
          'file' => $def['theme file'],
135
          'path' => $def['theme path'],
136
          'variables' => $variables[$type],
137
        );
138

    
139
        $include = DRUPAL_ROOT . '/' . $def['theme path'] . '/' . $def['theme file'];
140
        if (file_exists($include)) {
141
          require_once $include;
142
        }
143

    
144
        if (!function_exists('theme_' . $def['theme'])) {
145
          $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
146
        }
147
      }
148
      if (isset($def['additional themes'])) {
149
        foreach ($def['additional themes'] as $theme => $theme_type) {
150
          if (empty($theme_type)) {
151
            $theme = $theme_type;
152
            $theme_type = $type;
153
          }
154

    
155
          $hooks[$theme] = array(
156
            'pattern' => $theme . '__',
157
            'file' => $def['theme file'],
158
            'path' => $def['theme path'],
159
            'variables' => $variables[$theme_type],
160
          );
161

    
162
          if (!function_exists('theme_' . $theme)) {
163
            $hooks[$theme]['template'] = drupal_clean_css_identifier($theme);
164
          }
165
        }
166
      }
167
    }
168
  }
169

    
170
  $hooks['views_form_views_form'] = $base + array(
171
    'render element' => 'form',
172
  );
173

    
174
  $hooks['views_exposed_form'] = $base + array(
175
    'template' => 'views-exposed-form',
176
    'pattern' => 'views_exposed_form__',
177
    'render element' => 'form',
178
  );
179

    
180
  $hooks['views_more'] = $base + array(
181
    'template' => 'views-more',
182
    'pattern' => 'views_more__',
183
    'variables' => array('more_url' => NULL, 'link_text' => 'more', 'view' => NULL),
184
  );
185

    
186
  // Add theme suggestions which are part of modules.
187
  foreach (views_get_module_apis() as $info) {
188
    if (isset($info['template path'])) {
189
      $hooks += _views_find_module_templates($hooks, $info['template path']);
190
    }
191
  }
192
  return $hooks;
193
}
194

    
195
/**
196
 * Scans a directory of a module for template files.
197
 *
198
 * @param $cache
199
 *   The existing cache of theme hooks to test against.
200
 * @param $path
201
 *   The path to search.
202
 *
203
 * @see drupal_find_theme_templates()
204
 */
205
function _views_find_module_templates($cache, $path) {
206
  $templates = array();
207
  $regex = '/' . '\.tpl\.php' . '$' . '/';
208

    
209
  // Because drupal_system_listing works the way it does, we check for real
210
  // templates separately from checking for patterns.
211
  $files = drupal_system_listing($regex, $path, 'name', 0);
212
  foreach ($files as $template => $file) {
213
    // Chop off the remaining extensions if there are any. $template already
214
    // has the rightmost extension removed, but there might still be more,
215
    // such as with .tpl.php, which still has .tpl in $template at this point.
216
    if (($pos = strpos($template, '.')) !== FALSE) {
217
      $template = substr($template, 0, $pos);
218
    }
219
    // Transform - in filenames to _ to match function naming scheme
220
    // for the purposes of searching.
221
    $hook = strtr($template, '-', '_');
222
    if (isset($cache[$hook])) {
223
      $templates[$hook] = array(
224
        'template' => $template,
225
        'path' => dirname($file->filename),
226
        'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL,
227
      );
228
    }
229
    // Ensure that the pattern is maintained from base themes to its sub-themes.
230
    // Each sub-theme will have their templates scanned so the pattern must be
231
    // held for subsequent runs.
232
    if (isset($cache[$hook]['pattern'])) {
233
      $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
234
    }
235
  }
236

    
237
  $patterns = array_keys($files);
238

    
239
  foreach ($cache as $hook => $info) {
240
    if (!empty($info['pattern'])) {
241
      // Transform _ in pattern to - to match file naming scheme
242
      // for the purposes of searching.
243
      $pattern = strtr($info['pattern'], '_', '-');
244

    
245
      $matches = preg_grep('/^'. $pattern .'/', $patterns);
246
      if ($matches) {
247
        foreach ($matches as $match) {
248
          $file = substr($match, 0, strpos($match, '.'));
249
          // Put the underscores back in for the hook name and register this pattern.
250
          $templates[strtr($file, '-', '_')] = array(
251
            'template' => $file,
252
            'path' => dirname($files[$match]->uri),
253
            'variables' => isset($info['variables']) ? $info['variables'] : NULL,
254
            'render element' => isset($info['render element']) ? $info['render element'] : NULL,
255
            'base hook' => $hook,
256
            'includes' => isset($info['includes']) ? $info['includes'] : NULL,
257
          );
258
        }
259
      }
260
    }
261
  }
262

    
263
  return $templates;
264
}
265

    
266
/**
267
 * Returns a list of plugins and metadata about them.
268
 *
269
 * @return array
270
 *   An array keyed by PLUGIN_TYPE:PLUGIN_NAME, like 'display:page' or
271
 *   'pager:full', containing an array with the following keys:
272
 *   - title: The plugin's title.
273
 *   - type: The plugin type.
274
 *   - module: The module providing the plugin.
275
 *   - views: An array of enabled Views that are currently using this plugin,
276
 *     keyed by machine name.
277
 */
278
function views_plugin_list() {
279
  $plugin_data = views_fetch_plugin_data();
280
  $plugins = array();
281
  foreach (views_get_enabled_views() as $view) {
282
    foreach ($view->display as $display_id => $display) {
283
      foreach ($plugin_data as $type => $info) {
284
        if ($type == 'display' && isset($display->display_plugin)) {
285
          $name = $display->display_plugin;
286
        }
287
        elseif (isset($display->display_options["{$type}_plugin"])) {
288
          $name = $display->display_options["{$type}_plugin"];
289
        }
290
        elseif (isset($display->display_options[$type]['type'])) {
291
          $name = $display->display_options[$type]['type'];
292
        }
293
        else {
294
          continue;
295
        }
296

    
297
        // Key first by the plugin type, then the name.
298
        $key = $type . ':' . $name;
299
        // Add info for this plugin.
300
        if (!isset($plugins[$key])) {
301
          $plugins[$key] = array(
302
            'type' => $type,
303
            'title' => check_plain($info[$name]['title']),
304
            'module' => check_plain($info[$name]['module']),
305
            'views' => array(),
306
          );
307
        }
308

    
309
        // Add this view to the list for this plugin.
310
        $plugins[$key]['views'][$view->name] = $view->name;
311
      }
312
    }
313
  }
314
  return $plugins;
315
}
316

    
317
/**
318
 * A theme preprocess function to automatically allow view-based node
319
 * templates if called from a view.
320
 *
321
 * The 'modules/node.views.inc' file is a better place for this, but
322
 * we haven't got a chance to load that file before Drupal builds the
323
 * node portion of the theme registry.
324
 */
325
function views_preprocess_node(&$vars) {
326
  // The 'view' attribute of the node is added in views_preprocess_node()
327
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
328
    $vars['view'] = $vars['node']->view;
329
    $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name;
330
    if (!empty($vars['node']->view->current_display)) {
331
      $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
332

    
333
      // If a node is being rendered in a view, and the view does not have a path,
334
      // prevent drupal from accidentally setting the $page variable:
335
      if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
336
        $vars['page'] = FALSE;
337
      }
338
    }
339
  }
340

    
341
  // Allow to alter comments and links based on the settings in the row plugin.
342
  if (!empty($vars['view']->style_plugin->row_plugin) && get_class($vars['view']->style_plugin->row_plugin) == 'views_plugin_row_node_view') {
343
    node_row_node_view_preprocess_node($vars);
344
  }
345
}
346

    
347
/**
348
 * A theme preprocess function to automatically allow view-based node
349
 * templates if called from a view.
350
 */
351
function views_preprocess_comment(&$vars) {
352
  // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
353
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
354
    $vars['view'] = &$vars['node']->view;
355
    $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name;
356
    if (!empty($vars['node']->view->current_display)) {
357
      $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
358
    }
359
  }
360
}
361

    
362
/**
363
 * Implement hook_permission().
364
 */
365
function views_permission() {
366
  return array(
367
    'administer views' => array(
368
      'title' => t('Administer views'),
369
      'description' => t('Access the views administration pages.'),
370
      'restrict access' => TRUE,
371
    ),
372
    'access all views' => array(
373
      'title' => t('Bypass views access control'),
374
      'description' => t('Bypass access control when accessing views.'),
375
      'restrict access' => TRUE,
376
    ),
377
  );
378
}
379

    
380
/**
381
 * Implement hook_menu().
382
 */
383
function views_menu() {
384
  $items = array();
385
  $items['views/ajax'] = array(
386
    'title' => 'Views',
387
    'page callback' => 'views_ajax',
388
    'theme callback' => 'ajax_base_page_theme',
389
    'delivery callback' => 'ajax_deliver',
390
    'access callback' => TRUE,
391
    'description' => 'Ajax callback for view loading.',
392
    'type' => MENU_CALLBACK,
393
    'file' => 'includes/ajax.inc',
394
  );
395
  // Path is not admin/structure/views due to menu complications with the wildcards from
396
  // the generic ajax callback.
397
  $items['admin/views/ajax/autocomplete/user'] = array(
398
    'page callback' => 'views_ajax_autocomplete_user',
399
    'theme callback' => 'ajax_base_page_theme',
400
    'access callback' => 'user_access',
401
    'access arguments' => array('access user profiles'),
402
    'type' => MENU_CALLBACK,
403
    'file' => 'includes/ajax.inc',
404
  );
405
  // Define another taxonomy autocomplete because the default one of drupal
406
  // does not support a vid a argument anymore.
407
  $items['admin/views/ajax/autocomplete/taxonomy'] = array(
408
    'page callback' => 'views_ajax_autocomplete_taxonomy',
409
    'theme callback' => 'ajax_base_page_theme',
410
    'access callback' => 'user_access',
411
    'access arguments' => array('access content'),
412
    'type' => MENU_CALLBACK,
413
    'file' => 'includes/ajax.inc',
414
  );
415
  return $items;
416
}
417

    
418
/**
419
 * Implement hook_menu_alter().
420
 */
421
function views_menu_alter(&$callbacks) {
422
  $our_paths = array();
423
  $views = views_get_applicable_views('uses hook menu');
424
  foreach ($views as $data) {
425
    list($view, $display_id) = $data;
426
    $result = $view->execute_hook_menu($display_id, $callbacks);
427
    if (is_array($result)) {
428
      // The menu system doesn't support having two otherwise
429
      // identical paths with different placeholders.  So we
430
      // want to remove the existing items from the menu whose
431
      // paths would conflict with ours.
432

    
433
      // First, we must find any existing menu items that may
434
      // conflict.  We use a regular expression because we don't
435
      // know what placeholders they might use.  Note that we
436
      // first construct the regex itself by replacing %views_arg
437
      // in the display path, then we use this constructed regex
438
      // (which will be something like '#^(foo/%[^/]*/bar)$#') to
439
      // search through the existing paths.
440
      $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
441
      $matches = preg_grep($regex, array_keys($callbacks));
442

    
443
      // Remove any conflicting items that were found.
444
      foreach ($matches as $path) {
445
        // Don't remove the paths we just added!
446
        if (!isset($our_paths[$path])) {
447
          unset($callbacks[$path]);
448
        }
449
      }
450
      foreach ($result as $path => $item) {
451
        if (!isset($callbacks[$path])) {
452
          // Add a new item, possibly replacing (and thus effectively
453
          // overriding) one that we removed above.
454
          $callbacks[$path] = $item;
455
        }
456
        else {
457
          // This item already exists, so it must be one that we added.
458
          // We change the various callback arguments to pass an array
459
          // of possible display IDs instead of a single ID.
460
          $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
461
          $callbacks[$path]['page arguments'][1][] = $display_id;
462
          $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
463
          $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
464
          $callbacks[$path]['load arguments'][1][] = $display_id;
465
        }
466
        $our_paths[$path] = TRUE;
467
      }
468
    }
469
  }
470

    
471
  // Save memory: Destroy those views.
472
  foreach ($views as $data) {
473
    list($view, $display_id) = $data;
474
    $view->destroy();
475
  }
476
}
477

    
478
/**
479
 * Helper function for menu loading. This will automatically be
480
 * called in order to 'load' a views argument; primarily it
481
 * will be used to perform validation.
482
 *
483
 * @param $value
484
 *   The actual value passed.
485
 * @param $name
486
 *   The name of the view. This needs to be specified in the 'load function'
487
 *   of the menu entry.
488
 * @param $display_id
489
 *   The display id that will be loaded for this menu item.
490
 * @param $index
491
 *   The menu argument index. This counts from 1.
492
 */
493
function views_arg_load($value, $name, $display_id, $index) {
494
 static $views = array();
495

    
496
  $display_ids = is_array($display_id) ? $display_id : array($display_id);
497
  $display_id = reset($display_ids);
498

    
499
  foreach ($display_ids as $id) {
500
    // Make sure we haven't already loaded this views argument for a similar
501
    // menu item elsewhere. Since access is always checked for the current user,
502
    // we are sure that the static cache contains valid entries.
503
    $key = $name . ':' . $id . ':' . $value . ':' . $index;
504
    if (isset($views[$key])) {
505
      return $views[$key];
506
    }
507
    // Lazy load the view object to avoid unnecessary work.
508
    if (!isset($view)) {
509
      $view = views_get_view($name);
510
    }
511
    // Pick the first display we have access to.
512
    if ($view && count($display_ids) > 1 && $view->access($id)) {
513
      $display_id = $id;
514
      break;
515
    }
516
  }
517

    
518
  if ($view) {
519
    $view->set_display($display_id);
520
    $view->init_handlers();
521

    
522
    $ids = array_keys($view->argument);
523

    
524
    $indexes = array();
525
    $path = explode('/', $view->get_path());
526

    
527
    foreach ($path as $id => $piece) {
528
      if ($piece == '%' && !empty($ids)) {
529
        $indexes[$id] = array_shift($ids);
530
      }
531
    }
532

    
533
    if (isset($indexes[$index])) {
534
      if (isset($view->argument[$indexes[$index]])) {
535
        $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
536
        $view->destroy();
537

    
538
        // Store the output in case we load this same menu item again.
539
        $views[$key] = $arg;
540
        return $arg;
541
      }
542
    }
543
    $view->destroy();
544
  }
545
}
546

    
547
/**
548
 * Page callback: Displays a page view, given a name and display id.
549
 *
550
 * @param $name
551
 *   The name of a view.
552
 * @param $display_id
553
 *   The display id of a view.
554
 *
555
 * @return
556
 *   Either the HTML of a fully-executed view, or MENU_NOT_FOUND.
557
 */
558
function views_page($name, $display_id) {
559
  $args = func_get_args();
560
  // Remove $name and $display_id from the arguments.
561
  array_shift($args);
562
  array_shift($args);
563

    
564
  // Load the view and render it.
565
  if ($view = views_get_view($name)) {
566
    return $view->execute_display($display_id, $args);
567
  }
568

    
569
  // Fallback; if we get here no view was found or handler was not valid.
570
  return MENU_NOT_FOUND;
571
}
572

    
573
/**
574
 * Implements hook_page_alter().
575
 */
576
function views_page_alter(&$page) {
577
  // If the main content of this page contains a view, attach its contextual
578
  // links to the overall page array. This allows them to be rendered directly
579
  // next to the page title.
580
  $view = views_get_page_view();
581
  if (!empty($view)) {
582
    // If a module is still putting in the display like we used to, catch that.
583
    if (is_subclass_of($view, 'views_plugin_display')) {
584
      $view = $view->view;
585
    }
586

    
587
    views_add_contextual_links($page, 'page', $view, $view->current_display);
588
  }
589
}
590

    
591
/**
592
 * Implements MODULE_preprocess_HOOK() for html.tpl.php.
593
 */
594
function views_preprocess_html(&$variables) {
595
  // If the page contains a view as its main content, contextual links may have
596
  // been attached to the page as a whole; for example, by views_page_alter().
597
  // This allows them to be associated with the page and rendered by default
598
  // next to the page title (which we want). However, it also causes the
599
  // Contextual Links module to treat the wrapper for the entire page (i.e.,
600
  // the <body> tag) as the HTML element that these contextual links are
601
  // associated with. This we don't want; for better visual highlighting, we
602
  // prefer a smaller region to be chosen. The region we prefer differs from
603
  // theme to theme and depends on the details of the theme's markup in
604
  // page.tpl.php, so we can only find it using JavaScript. We therefore remove
605
  // the "contextual-links-region" class from the <body> tag here and add
606
  // JavaScript that will insert it back in the correct place.
607
  if (!empty($variables['page']['#views_contextual_links_info'])) {
608
    $key = array_search('contextual-links-region', $variables['classes_array']);
609
    if ($key !== FALSE) {
610
      $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
611
      // Add the JavaScript, with a group and weight such that it will run
612
      // before modules/contextual/contextual.js.
613
      drupal_add_js(drupal_get_path('module', 'views') . '/js/views-contextual.js', array('group' => JS_LIBRARY, 'weight' => -1));
614
    }
615
  }
616
}
617

    
618
/**
619
* Implements hook_preprocess_HOOK() for page.tpl.php.
620
*/
621
function views_preprocess_page(&$variables) {
622
  // If the page contains a view as its main content, contextual links may have
623
  // been attached to the page as a whole; for example, by views_page_alter().
624
  // This allows them to be associated with the page and rendered by default
625
  // next to the page title (which we want). However, it also causes the
626
  // Contextual Links module to treat the wrapper for the entire page (i.e.,
627
  // the <body> tag) as the HTML element that these contextual links are
628
  // associated with. This we don't want; for better visual highlighting, we
629
  // prefer a smaller region to be chosen. The region we prefer differs from
630
  // theme to theme and depends on the details of the theme's markup in
631
  // page.tpl.php, so we can only find it using JavaScript. We therefore remove
632
  // the "contextual-links-region" class from the <body> tag here and add
633
  // JavaScript that will insert it back in the correct place.
634
  if (!empty($variables['page']['#views_contextual_links_info'])) {
635
    $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
636
  }
637
}
638

    
639
/**
640
 * Implements hook_contextual_links_view_alter().
641
 */
642
function views_contextual_links_view_alter(&$element, $items) {
643
  // If we are rendering views-related contextual links attached to the overall
644
  // page array, add a class to the list of contextual links. This will be used
645
  // by the JavaScript added in views_preprocess_html().
646
  if (!empty($element['#element']['#views_contextual_links_info']) && !empty($element['#element']['#type']) && $element['#element']['#type'] == 'page') {
647
    $element['#attributes']['class'][] = 'views-contextual-links-page';
648
  }
649
}
650

    
651
/**
652
 * Implement hook_block_info().
653
 */
654
function views_block_info() {
655
  // Try to avoid instantiating all the views just to get the blocks info.
656
  views_include('cache');
657
  $cache = views_cache_get('views_block_items', TRUE);
658
  if ($cache && is_array($cache->data)) {
659
    return $cache->data;
660
  }
661

    
662
  $items = array();
663
  $views = views_get_all_views();
664
  foreach ($views as $view) {
665
    // disabled views get nothing.
666
    if (!empty($view->disabled)) {
667
      continue;
668
    }
669

    
670
    $view->init_display();
671
    foreach ($view->display as $display_id => $display) {
672

    
673
      if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
674
        $result = $display->handler->execute_hook_block_list();
675
        if (is_array($result)) {
676
          $items = array_merge($items, $result);
677
        }
678
      }
679

    
680
      if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
681
        $result = $display->handler->get_special_blocks();
682
        if (is_array($result)) {
683
          $items = array_merge($items, $result);
684
        }
685
      }
686
    }
687
  }
688

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

    
694
  // get the keys because we're modifying the array and we don't want to
695
  // confuse PHP too much.
696
  $keys = array_keys($items);
697
  foreach ($keys as $delta) {
698
    if (strlen($delta) >= 32) {
699
      $hash = md5($delta);
700
      $hashes[$hash] = $delta;
701
      $items[$hash] = $items[$delta];
702
      unset($items[$delta]);
703
    }
704
  }
705

    
706
  // Only save hashes if they have changed.
707
  $old_hashes = variable_get('views_block_hashes', array());
708
  if ($hashes != $old_hashes) {
709
    variable_set('views_block_hashes', $hashes);
710
  }
711
  // Save memory: Destroy those views.
712
  foreach ($views as $view) {
713
    $view->destroy();
714
  }
715

    
716
  views_cache_set('views_block_items', $items, TRUE);
717

    
718
  return $items;
719
}
720

    
721
/**
722
 * Implement hook_block_view().
723
 */
724
function views_block_view($delta) {
725
  $start = microtime(TRUE);
726
  // if this is 32, this should be an md5 hash.
727
  if (strlen($delta) == 32) {
728
    $hashes = variable_get('views_block_hashes', array());
729
    if (!empty($hashes[$delta])) {
730
      $delta = $hashes[$delta];
731
    }
732
  }
733

    
734
  // This indicates it's a special one.
735
  if (substr($delta, 0, 1) == '-') {
736
    list($nothing, $type, $name, $display_id) = explode('-', $delta);
737
    // Put the - back on.
738
    $type = '-' . $type;
739
    if ($view = views_get_view($name)) {
740
      if ($view->access($display_id)) {
741
        $view->set_display($display_id);
742
        if (isset($view->display_handler)) {
743
          $output = $view->display_handler->view_special_blocks($type);
744
          // Before returning the block output, convert it to a renderable
745
          // array with contextual links.
746
          views_add_block_contextual_links($output, $view, $display_id, 'special_block_' . $type);
747
          $view->destroy();
748
          return $output;
749
        }
750
      }
751
      $view->destroy();
752
    }
753
  }
754

    
755
  // If the delta doesn't contain valid data return nothing.
756
  $explode = explode('-', $delta);
757
  if (count($explode) != 2) {
758
    return;
759
  }
760
  list($name, $display_id) = $explode;
761
  // Load the view
762
  if ($view = views_get_view($name)) {
763
    if ($view->access($display_id)) {
764
      $output = $view->execute_display($display_id);
765
      // Before returning the block output, convert it to a renderable array
766
      // with contextual links.
767
      views_add_block_contextual_links($output, $view, $display_id);
768
      $view->destroy();
769
      return $output;
770
    }
771
    $view->destroy();
772
  }
773
}
774

    
775
/**
776
 * Converts Views block content to a renderable array with contextual links.
777
 *
778
 * @param $block
779
 *   An array representing the block, with the same structure as the return
780
 *   value of hook_block_view(). This will be modified so as to force
781
 *   $block['content'] to be a renderable array, containing the optional
782
 *   '#contextual_links' property (if there are any contextual links associated
783
 *   with the block).
784
 * @param $view
785
 *   The view that was used to generate the block content.
786
 * @param $display_id
787
 *   The ID of the display within the view that was used to generate the block
788
 *   content.
789
 * @param $block_type
790
 *   The type of the block. If it's block it's a regular views display,
791
 *   but 'special_block_-exp' exist as well.
792
 */
793
function views_add_block_contextual_links(&$block, $view, $display_id, $block_type = 'block') {
794
  // Do not add contextual links to an empty block.
795
  if (!empty($block['content'])) {
796
    // Contextual links only work on blocks whose content is a renderable
797
    // array, so if the block contains a string of already-rendered markup,
798
    // convert it to an array.
799
    if (is_string($block['content'])) {
800
      $block['content'] = array('#markup' => $block['content']);
801
    }
802
    // Add the contextual links.
803
    views_add_contextual_links($block['content'], $block_type, $view, $display_id);
804
  }
805
}
806

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

    
920
/**
921
 * Returns an array of language names.
922
 *
923
 * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
924
 *
925
 * @param $field
926
 *   'name' => names in current language, localized
927
 *   'native' => native names
928
 * @param $all
929
 *   Boolean to return all languages or only enabled ones
930
 *
931
 * @see locale_language_list()
932
 */
933
function views_language_list($field = 'name', $all = FALSE) {
934
  if ($all) {
935
    $languages = language_list();
936
  }
937
  else {
938
    $languages = language_list('enabled');
939
    $languages = $languages[1];
940
  }
941
  $list = array();
942
  foreach ($languages as $language) {
943
    $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
944
  }
945
  return $list;
946
}
947

    
948
/**
949
 * Implements hook_flush_caches().
950
 */
951
function views_flush_caches() {
952
  return array('cache_views', 'cache_views_data');
953
}
954

    
955
/**
956
 * Implements hook_field_create_instance.
957
 */
958
function views_field_create_instance($instance) {
959
  cache_clear_all('*', 'cache_views', TRUE);
960
  cache_clear_all('*', 'cache_views_data', TRUE);
961
}
962

    
963
/**
964
 * Implements hook_field_update_instance.
965
 */
966
function views_field_update_instance($instance, $prior_instance) {
967
  cache_clear_all('*', 'cache_views', TRUE);
968
  cache_clear_all('*', 'cache_views_data', TRUE);
969
}
970

    
971
/**
972
 * Implements hook_field_delete_instance.
973
 */
974
function views_field_delete_instance($instance) {
975
  cache_clear_all('*', 'cache_views', TRUE);
976
  cache_clear_all('*', 'cache_views_data', TRUE);
977
}
978

    
979
/**
980
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
981
 */
982
function views_invalidate_cache() {
983
  // Clear the views cache.
984
  cache_clear_all('*', 'cache_views', TRUE);
985

    
986
  // Clear the page and block cache.
987
  cache_clear_all();
988

    
989
  // Set the menu as needed to be rebuilt.
990
  variable_set('menu_rebuild_needed', TRUE);
991

    
992
  // Allow modules to respond to the Views cache being cleared.
993
  module_invoke_all('views_invalidate_cache');
994
}
995

    
996
/**
997
 * Access callback to determine if the user can import Views.
998
 *
999
 * View imports require an additional access check because they are PHP
1000
 * code and PHP is more locked down than administer views.
1001
 */
1002
function views_import_access() {
1003
  return user_access('administer views') && user_access('use PHP for settings');
1004
}
1005

    
1006
/**
1007
 * Determine if the logged in user has access to a view.
1008
 *
1009
 * This function should only be called from a menu hook or some other
1010
 * embedded source. Each argument is the result of a call to
1011
 * views_plugin_access::get_access_callback() which is then used
1012
 * to determine if that display is accessible. If *any* argument
1013
 * is accessible, then the view is accessible.
1014
 */
1015
function views_access() {
1016
  $args = func_get_args();
1017
  foreach ($args as $arg) {
1018
    if ($arg === TRUE) {
1019
      return TRUE;
1020
    }
1021

    
1022
    if (!is_array($arg)) {
1023
      continue;
1024
    }
1025

    
1026
    list($callback, $arguments) = $arg;
1027
    $arguments = $arguments ? $arguments : array();
1028
    // Bring dynamic arguments to the access callback.
1029
    foreach ($arguments as $key => $value) {
1030
      if (is_int($value) && isset($args[$value])) {
1031
        $arguments[$key] = $args[$value];
1032
      }
1033
    }
1034
    if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
1035
      return TRUE;
1036
    }
1037
  }
1038

    
1039
  return FALSE;
1040
}
1041

    
1042
/**
1043
 * Access callback for the views_plugin_access_perm access plugin.
1044
 *
1045
 * Determine if the specified user has access to a view on the basis of
1046
 * permissions. If the $account argument is omitted, the current user
1047
 * is used.
1048
 */
1049
function views_check_perm($perms, $account = NULL) {
1050
  // Backward compatibility to ensure also a single permission string is
1051
  // properly processed.
1052
  $perms = is_array($perms) ? $perms : array($perms);
1053
  if (user_access('access all views', $account)) {
1054
    return TRUE;
1055
  }
1056
  // Perms are handled as OR, as soon one permission allows access TRUE is
1057
  // returned.
1058
  foreach ($perms as $perm) {
1059
    if (user_access($perm, $account)) {
1060
      return TRUE;
1061
    }
1062
  }
1063
  return FALSE;
1064
}
1065

    
1066
/**
1067
 * Access callback for the views_plugin_access_role access plugin.
1068

    
1069
 * Determine if the specified user has access to a view on the basis of any of
1070
 * the requested roles. If the $account argument is omitted, the current user
1071
 * is used.
1072
 */
1073
function views_check_roles($rids, $account = NULL) {
1074
  global $user;
1075
  $account = isset($account) ? $account : $user;
1076
  $roles = array_keys($account->roles);
1077
  $roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
1078
  return user_access('access all views', $account) || array_intersect(array_filter($rids), $roles);
1079
}
1080
// ------------------------------------------------------------------
1081
// Functions to help identify views that are running or ran
1082

    
1083
/**
1084
 * Set the current 'page view' that is being displayed so that it is easy
1085
 * for other modules or the theme to identify.
1086
 */
1087
function &views_set_page_view($view = NULL) {
1088
  static $cache = NULL;
1089
  if (isset($view)) {
1090
    $cache = $view;
1091
  }
1092

    
1093
  return $cache;
1094
}
1095

    
1096
/**
1097
 * Find out what, if any, page view is currently in use. Please note that
1098
 * this returns a reference, so be careful! You can unintentionally modify the
1099
 * $view object.
1100
 *
1101
 * @return view
1102
 *   A fully formed, empty $view object.
1103
 */
1104
function &views_get_page_view() {
1105
  return views_set_page_view();
1106
}
1107

    
1108
/**
1109
 * Set the current 'current view' that is being built/rendered so that it is
1110
 * easy for other modules or items in drupal_eval to identify
1111
 *
1112
 * @return view
1113
 */
1114
function &views_set_current_view($view = NULL) {
1115
  static $cache = NULL;
1116
  if (isset($view)) {
1117
    $cache = $view;
1118
  }
1119

    
1120
  return $cache;
1121
}
1122

    
1123
/**
1124
 * Find out what, if any, current view is currently in use. Please note that
1125
 * this returns a reference, so be careful! You can unintentionally modify the
1126
 * $view object.
1127
 *
1128
 * @return view
1129
 */
1130
function &views_get_current_view() {
1131
  return views_set_current_view();
1132
}
1133

    
1134
// ------------------------------------------------------------------
1135
// Include file helpers
1136

    
1137
/**
1138
 * Include views .inc files as necessary.
1139
 */
1140
function views_include($file) {
1141
  ctools_include($file, 'views');
1142
}
1143

    
1144
/**
1145
 * Load views files on behalf of modules.
1146
 */
1147
function views_module_include($api, $reset = FALSE) {
1148
  if ($reset) {
1149
    $cache = &drupal_static('ctools_plugin_api_info');
1150
    if (isset($cache['views']['views'])) {
1151
      unset($cache['views']['views']);
1152
    }
1153
  }
1154
  ctools_include('plugins');
1155
  return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version());
1156
}
1157

    
1158
/**
1159
 * Get a list of modules that support the current views API.
1160
 */
1161
function views_get_module_apis($api = 'views', $reset = FALSE) {
1162
  if ($reset) {
1163
    $cache = &drupal_static('ctools_plugin_api_info');
1164
    if (isset($cache['views']['views'])) {
1165
      unset($cache['views']['views']);
1166
    }
1167
  }
1168
  ctools_include('plugins');
1169
  return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version());
1170
}
1171

    
1172
/**
1173
 * Include views .css files.
1174
 */
1175
function views_add_css($file) {
1176
  // We set preprocess to FALSE because we are adding the files conditionally,
1177
  // and we don't want to generate duplicate cache files.
1178
  // TODO: at some point investigate adding some files unconditionally and
1179
  // allowing preprocess.
1180
  drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE));
1181
}
1182

    
1183
/**
1184
 * Include views .js files.
1185
 */
1186
function views_add_js($file) {
1187
  // If javascript has been disabled by the user, never add js files.
1188
  if (variable_get('views_no_javascript', FALSE)) {
1189
    return;
1190
  }
1191
  static $base = TRUE, $ajax = TRUE;
1192
  if ($base) {
1193
    drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
1194
    $base = FALSE;
1195
  }
1196
  if ($ajax && in_array($file, array('ajax', 'ajax_view'))) {
1197
    drupal_add_library('system', 'drupal.ajax');
1198
    drupal_add_library('system', 'jquery.form');
1199
    $ajax = FALSE;
1200
  }
1201
  ctools_add_js($file, 'views');
1202
}
1203

    
1204
/**
1205
 * Load views files on behalf of modules.
1206
 */
1207
function views_include_handlers($reset = FALSE) {
1208
  static $finished = FALSE;
1209
  // Ensure this only gets run once.
1210
  if ($finished && !$reset) {
1211
    return;
1212
  }
1213

    
1214
  views_include('base');
1215
  views_include('handlers');
1216
  views_include('cache');
1217
  views_include('plugins');
1218
  views_module_include('views', $reset);
1219
  $finished = TRUE;
1220
}
1221

    
1222
// -----------------------------------------------------------------------
1223
// Views handler functions
1224

    
1225
/**
1226
 * Fetch a handler from the data cache.
1227
 *
1228
 * @param $table
1229
 *   The name of the table this handler is from.
1230
 * @param $field
1231
 *   The name of the field this handler is from.
1232
 * @param $key
1233
 *   The type of handler. i.e, sort, field, argument, filter, relationship
1234
 * @param $override
1235
 *   Override the actual handler object with this class. Used for
1236
 *   aggregation when the handler is redirected to the aggregation
1237
 *   handler.
1238
 *
1239
 * @return views_handler
1240
 *   An instance of a handler object. May be views_handler_broken.
1241
 */
1242
function views_get_handler($table, $field, $key, $override = NULL) {
1243
  static $recursion_protection = array();
1244

    
1245
  $data = views_fetch_data($table, FALSE);
1246
  $handler = NULL;
1247
  views_include('handlers');
1248

    
1249
  // Support old views_data entries conversion.
1250

    
1251
  // Support conversion on table level.
1252
  if (isset($data['moved to'])) {
1253
    $moved = array($data['moved to'], $field);
1254
  }
1255
  // Support conversion on datafield level.
1256
  if (isset($data[$field]['moved to'])) {
1257
    $moved = $data[$field]['moved to'];
1258
  }
1259
  // Support conversion on handler level.
1260
  if (isset($data[$field][$key]['moved to'])) {
1261
    $moved = $data[$field][$key]['moved to'];
1262
  }
1263

    
1264
  if (isset($data[$field][$key]) || !empty($moved)) {
1265
    if (!empty($moved)) {
1266
      list($moved_table, $moved_field) = $moved;
1267
      if (!empty($recursion_protection[$moved_table][$moved_field])) {
1268
        // recursion detected!
1269
        return NULL;
1270
      }
1271

    
1272
      $recursion_protection[$moved_table][$moved_field] = TRUE;
1273
      $handler = views_get_handler($moved_table, $moved_field, $key, $override);
1274
      $recursion_protection = array();
1275
      if ($handler) {
1276
        // store these values so we know what we were originally called.
1277
        $handler->original_table = $table;
1278
        $handler->original_field = $field;
1279
        if (empty($handler->actual_table)) {
1280
          $handler->actual_table = $moved_table;
1281
          $handler->actual_field = $moved_field;
1282
        }
1283
      }
1284
      return $handler;
1285
    }
1286

    
1287
    // Set up a default handler:
1288
    if (empty($data[$field][$key]['handler'])) {
1289
      $data[$field][$key]['handler'] = 'views_handler_' . $key;
1290
    }
1291

    
1292
    if ($override) {
1293
      $data[$field][$key]['override handler'] = $override;
1294
    }
1295

    
1296
    $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
1297
  }
1298

    
1299
  if ($handler) {
1300
    return $handler;
1301
  }
1302

    
1303
  // DEBUG -- identify missing handlers
1304
  vpr("Missing handler: @table @field @key", array('@table' => $table, '@field' => $field, '@key' => $key));
1305
  $broken = array(
1306
    'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
1307
    'handler' => 'views_handler_' . $key . '_broken',
1308
    'table' => $table,
1309
    'field' => $field,
1310
  );
1311
  return _views_create_handler($broken, 'handler', $key);
1312
}
1313

    
1314
/**
1315
 * Fetch Views' data from the cache
1316
 */
1317
function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
1318
  views_include('cache');
1319
  return _views_fetch_data($table, $move, $reset);
1320
}
1321

    
1322
// -----------------------------------------------------------------------
1323
// Views plugin functions
1324

    
1325
/**
1326
 * Fetch the plugin data from cache.
1327
 */
1328
function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
1329
  views_include('cache');
1330
  return _views_fetch_plugin_data($type, $plugin, $reset);
1331
}
1332

    
1333
/**
1334
 * Fetch a list of all base tables available
1335
 *
1336
 * @param $type
1337
 *   Either 'display', 'style' or 'row'
1338
 * @param $key
1339
 *   For style plugins, this is an optional type to restrict to. May be 'normal',
1340
 *   'summary', 'feed' or others based on the needs of the display.
1341
 * @param $base
1342
 *   An array of possible base tables.
1343
 *
1344
 * @return
1345
 *   A keyed array of in the form of 'base_table' => 'Description'.
1346
 */
1347
function views_fetch_plugin_names($type, $key = NULL, $base = array()) {
1348
  $data = views_fetch_plugin_data();
1349

    
1350
  $plugins[$type] = array();
1351

    
1352
  foreach ($data[$type] as $id => $plugin) {
1353
    // Skip plugins that don't conform to our key.
1354
    if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
1355
      continue;
1356
    }
1357
    if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) {
1358
      $plugins[$type][$id] = $plugin['title'];
1359
    }
1360
  }
1361

    
1362
  if (!empty($plugins[$type])) {
1363
    asort($plugins[$type]);
1364
    return $plugins[$type];
1365
  }
1366
  // fall-through
1367
  return array();
1368
}
1369

    
1370
/**
1371
 * Get a handler for a plugin
1372
 *
1373
 * @return views_plugin
1374
 *
1375
 * The created plugin object.
1376
 */
1377
function views_get_plugin($type, $plugin, $reset = FALSE) {
1378
  views_include('handlers');
1379
  $definition = views_fetch_plugin_data($type, $plugin, $reset);
1380
  if (!empty($definition)) {
1381
    return _views_create_handler($definition, $type);
1382
  }
1383
}
1384

    
1385
/**
1386
 * Load the current enabled localization plugin.
1387
 *
1388
 * @return The name of the localization plugin.
1389
 */
1390
function views_get_localization_plugin() {
1391
  $plugin = variable_get('views_localization_plugin', '');
1392
  // Provide sane default values for the localization plugin.
1393
  if (empty($plugin)) {
1394
    if (module_exists('locale')) {
1395
      $plugin = 'core';
1396
    }
1397
    else {
1398
      $plugin = 'none';
1399
    }
1400
  }
1401

    
1402
  return $plugin;
1403
}
1404

    
1405
// -----------------------------------------------------------------------
1406
// Views database functions
1407

    
1408
/**
1409
 * Get all view templates.
1410
 *
1411
 * Templates are special in-code views that are never active, but exist only
1412
 * to be cloned into real views as though they were templates.
1413
 */
1414
function views_get_all_templates() {
1415
  $templates = array();
1416
  $modules = views_module_include('views_template');
1417

    
1418
  foreach ($modules as $module => $info) {
1419
    $function = $module . '_views_templates';
1420
    if (function_exists($function)) {
1421
      $new = $function();
1422
      if ($new && is_array($new)) {
1423
        $templates = array_merge($new, $templates);
1424
      }
1425
    }
1426
  }
1427

    
1428
  return $templates;
1429
}
1430

    
1431
/**
1432
 * Create an empty view to work with.
1433
 *
1434
 * @return view
1435
 *   A fully formed, empty $view object. This object must be populated before
1436
 *   it can be successfully saved.
1437
 */
1438
function views_new_view() {
1439
  views_include('view');
1440
  $view = new view();
1441
  $view->vid = 'new';
1442
  $view->add_display('default');
1443

    
1444
  return $view;
1445
}
1446

    
1447
/**
1448
 * Return a list of all views and display IDs that have a particular
1449
 * setting in their display's plugin settings.
1450
 *
1451
 * @return
1452
 * @code
1453
 * array(
1454
 *   array($view, $display_id),
1455
 *   array($view, $display_id),
1456
 * );
1457
 * @endcode
1458
 */
1459
function views_get_applicable_views($type) {
1460
  // @todo: Use a smarter flagging system so that we don't have to
1461
  // load every view for this.
1462
  $result = array();
1463
  $views = views_get_all_views();
1464

    
1465
  foreach ($views as $view) {
1466
    // Skip disabled views.
1467
    if (!empty($view->disabled)) {
1468
      continue;
1469
    }
1470

    
1471
    if (empty($view->display)) {
1472
      // Skip this view as it is broken.
1473
      vsm(t("Skipping broken view @view", array('@view' => $view->name)));
1474
      continue;
1475
    }
1476

    
1477
    // Loop on array keys because something seems to muck with $view->display
1478
    // a bit in PHP4.
1479
    foreach (array_keys($view->display) as $id) {
1480
      $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
1481
      if (!empty($plugin[$type])) {
1482
        // This view uses hook menu. Clone it so that different handlers
1483
        // don't trip over each other, and add it to the list.
1484
        $v = $view->clone_view();
1485
        if ($v->set_display($id) && $v->display_handler->get_option('enabled')) {
1486
          $result[] = array($v, $id);
1487
        }
1488
        // In PHP 4.4.7 and presumably earlier, if we do not unset $v
1489
        // here, we will find that it actually overwrites references
1490
        // possibly due to shallow copying issues.
1491
        unset($v);
1492
      }
1493
    }
1494
  }
1495
  return $result;
1496
}
1497

    
1498
/**
1499
 * Return an array of all views as fully loaded $view objects.
1500
 *
1501
 * @param $reset
1502
 *   If TRUE, reset the static cache forcing views to be reloaded.
1503
 */
1504
function views_get_all_views($reset = FALSE) {
1505
  ctools_include('export');
1506
  return ctools_export_crud_load_all('views_view', $reset);
1507
}
1508

    
1509
/**
1510
 * Returns an array of all enabled views, as fully loaded $view objects.
1511
 */
1512
function views_get_enabled_views() {
1513
  $views = views_get_all_views();
1514
  return array_filter($views, 'views_view_is_enabled');
1515
}
1516

    
1517
/**
1518
 * Returns an array of all disabled views, as fully loaded $view objects.
1519
 */
1520
function views_get_disabled_views() {
1521
  $views = views_get_all_views();
1522
  return array_filter($views, 'views_view_is_disabled');
1523
}
1524

    
1525
/**
1526
 * Return an array of view as options array, that can be used by select,
1527
 * checkboxes and radios as #options.
1528
 *
1529
 * @param bool $views_only
1530
 *  If TRUE, only return views, not displays.
1531
 * @param string $filter
1532
 *  Filters the views on status. Can either be 'all' (default), 'enabled' or
1533
 *  'disabled'
1534
 * @param  mixed $exclude_view
1535
 *  view or current display to exclude
1536
 *  either a
1537
 *  - views object (containing $exclude_view->name and $exclude_view->current_display)
1538
 *  - views name as string:  e.g. my_view
1539
 *  - views name and display id (separated by ':'): e.g. my_view:default
1540
 * @param bool $optgroup
1541
 *  If TRUE, returns an array with optgroups for each view (will be ignored for
1542
 *  $views_only = TRUE). Can be used by select
1543
 * @param bool $sort
1544
 *  If TRUE, the list of views is sorted ascending.
1545
 *
1546
 * @return array
1547
 *  an associative array for use in select.
1548
 *  - key: view name and display id separated by ':', or the view name only
1549
 */
1550
function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE, $sort = FALSE) {
1551

    
1552
  // Filter the big views array.
1553
  switch ($filter) {
1554
    case 'all':
1555
    case 'disabled':
1556
    case 'enabled':
1557
      $func = "views_get_{$filter}_views";
1558
      $views = $func();
1559
      break;
1560
    default:
1561
      return array();
1562
  }
1563

    
1564
  // Prepare exclude view strings for comparison.
1565
  if (empty($exclude_view)) {
1566
    $exclude_view_name = '';
1567
    $exclude_view_display = '';
1568
  }
1569
  elseif (is_object($exclude_view)) {
1570
    $exclude_view_name = $exclude_view->name;
1571
    $exclude_view_display = $exclude_view->current_display;
1572
  }
1573
  else {
1574
    list($exclude_view_name, $exclude_view_display) = explode(':', $exclude_view);
1575
  }
1576

    
1577
  $options = array();
1578
  foreach ($views as $view) {
1579
    // Return only views.
1580
    if ($views_only && $view->name != $exclude_view_name) {
1581
      $options[$view->name] = $view->get_human_name();
1582
    }
1583
    // Return views with display ids.
1584
    else {
1585
      foreach ($view->display as $display_id => $display) {
1586
        if (!($view->name == $exclude_view_name && $display_id == $exclude_view_display)) {
1587
          if ($optgroup) {
1588
            $options[$view->name][$view->name . ':' . $display->id] = t('@view : @display', array('@view' => $view->name, '@display' => $display->id));
1589
          }
1590
          else {
1591
            $options[$view->name . ':' . $display->id] = t('View: @view - Display: @display', array('@view' => $view->name, '@display' => $display->id));
1592
          }
1593
        }
1594
      }
1595
    }
1596
  }
1597
  if ($sort) {
1598
    ksort($options);
1599
  }
1600
  return $options;
1601
}
1602

    
1603
/**
1604
 * Returns TRUE if a view is enabled, FALSE otherwise.
1605
 */
1606
function views_view_is_enabled($view) {
1607
  return empty($view->disabled);
1608
}
1609

    
1610
/**
1611
 * Returns TRUE if a view is disabled, FALSE otherwise.
1612
 */
1613
function views_view_is_disabled($view) {
1614
  return !empty($view->disabled);
1615
}
1616

    
1617
/**
1618
 * Get a view from the database or from default views.
1619
 *
1620
 * This function is just a static wrapper around views::load(). This function
1621
 * isn't called 'views_load()' primarily because it might get a view
1622
 * from the default views which aren't technically loaded from the database.
1623
 *
1624
 * @param $name
1625
 *   The name of the view.
1626
 * @param $reset
1627
 *   If TRUE, reset this entry in the load cache.
1628
 * @return view
1629
 *   A reference to the $view object. Use $reset if you're sure you want
1630
 *   a fresh one.
1631
 */
1632
function views_get_view($name, $reset = FALSE) {
1633
  if ($reset) {
1634
    $cache = &drupal_static('ctools_export_load_object');
1635
    if (isset($cache['views_view'][$name])) {
1636
      unset($cache['views_view'][$name]);
1637
    }
1638
  }
1639

    
1640
  ctools_include('export');
1641
  $view = ctools_export_crud_load('views_view', $name);
1642
  if ($view) {
1643
    $view->update();
1644
    return $view->clone_view();
1645
  }
1646
}
1647

    
1648
/**
1649
 * Find the real location of a table.
1650
 *
1651
 * If a table has moved, find the new name of the table so that we can
1652
 * change its name directly in options where necessary.
1653
 */
1654
function views_move_table($table) {
1655
  $data = views_fetch_data($table, FALSE);
1656
  if (isset($data['moved to'])) {
1657
    $table = $data['moved to'];
1658
  }
1659

    
1660
  return $table;
1661
}
1662

    
1663
/**
1664
 * Export callback to load the view subrecords, which are the displays.
1665
 */
1666
function views_load_display_records(&$views) {
1667
  // Get vids from the views.
1668
  $names = array();
1669
  foreach ($views as $view) {
1670
    if (empty($view->display)) {
1671
      $names[$view->vid] = $view->name;
1672
    }
1673
  }
1674

    
1675
  if (empty($names)) {
1676
    return;
1677
  }
1678

    
1679
  foreach (view::db_objects() as $key) {
1680
    $object_name = "views_$key";
1681
    $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
1682
      array(':vids' => array_keys($names)));
1683

    
1684
    foreach ($result as $data) {
1685
      $object = new $object_name(FALSE);
1686
      $object->load_row($data);
1687

    
1688
      // Because it can get complicated with this much indirection,
1689
      // make a shortcut reference.
1690
      $location = &$views[$names[$object->vid]]->$key;
1691

    
1692
      // If we have a basic id field, load the item onto the view based on
1693
      // this ID, otherwise push it on.
1694
      if (!empty($object->id)) {
1695
        $location[$object->id] = $object;
1696
      }
1697
      else {
1698
        $location[] = $object;
1699
      }
1700
    }
1701
  }
1702
}
1703

    
1704
/**
1705
 * Export CRUD callback to save a view.
1706
 */
1707
function views_save_view(&$view) {
1708
  return $view->save();
1709
}
1710

    
1711
/**
1712
 * Export CRUD callback to delete a view.
1713
 */
1714
function views_delete_view(&$view) {
1715
  return $view->delete(TRUE);
1716
}
1717

    
1718
/**
1719
 * Export CRUD callback to export a view.
1720
 */
1721
function views_export_view(&$view, $indent = '') {
1722
  return $view->export($indent);
1723
}
1724

    
1725
/**
1726
 * Export callback to change view status.
1727
 */
1728
function views_export_status($view, $status) {
1729
  ctools_export_set_object_status($view, $status);
1730
  views_invalidate_cache();
1731
}
1732

    
1733
// ------------------------------------------------------------------
1734
// Views debug helper functions
1735

    
1736
/**
1737
 * Provide debug output for Views.
1738
 *
1739
 * This relies on devel.module
1740
 * or on the debug() function if you use a simpletest.
1741
 *
1742
 * @param $message
1743
 *   The message/variable which should be debugged.
1744
 *   This either could be
1745
 *     * an array/object which is converted to pretty output
1746
 *     * a translation source string which is used together with the parameter placeholders.
1747
 *
1748
 * @param $placeholder
1749
 *   The placeholders which are used for the translation source string.
1750
 */
1751
function views_debug($message, $placeholders = array()) {
1752
  if (!is_string($message)) {
1753
    $output = '<pre>' . var_export($message, TRUE) . '</pre>';
1754
  }
1755
  if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
1756
    $devel_region = variable_get('views_devel_region', 'footer');
1757
    if ($devel_region == 'watchdog') {
1758
      $output = $message;
1759
      watchdog('views_logging', $output, $placeholders);
1760
    }
1761
    else if ($devel_region == 'drupal_debug') {
1762
      $output = empty($output) ? t($message, $placeholders) : $output;
1763
      dd($output);
1764
    }
1765
    else {
1766
      $output = empty($output) ? t($message, $placeholders) : $output;
1767
      dpm($output);
1768
    }
1769
  }
1770
  elseif (isset($GLOBALS['drupal_test_info'])) {
1771
    $output = empty($output) ? t($message, $placeholders) : $output;
1772
    debug($output);
1773
  }
1774
}
1775

    
1776
/**
1777
 * Shortcut to views_debug()
1778
 */
1779
function vpr($message, $placeholders = array()) {
1780
  views_debug($message, $placeholders);
1781
}
1782

    
1783
/**
1784
 * Debug messages
1785
 */
1786
function vsm($message) {
1787
  if (module_exists('devel')) {
1788
    dpm($message);
1789
  }
1790
}
1791

    
1792
function views_trace() {
1793
  $message = '';
1794
  foreach (debug_backtrace() as $item) {
1795
    if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
1796
      $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
1797
    }
1798
  }
1799
  return $message;
1800
}
1801

    
1802
function vsm_trace() {
1803
  vsm(views_trace());
1804
}
1805

    
1806
function vpr_trace() {
1807
  dpr(views_trace());
1808
}
1809

    
1810
// ------------------------------------------------------------------
1811
// Views form (View with form elements)
1812

    
1813
/**
1814
 * Returns TRUE if the passed-in view contains handlers with views form
1815
 * implementations, FALSE otherwise.
1816
 */
1817
function views_view_has_form_elements($view) {
1818
  foreach ($view->field as $field) {
1819
    if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
1820
      return TRUE;
1821
    }
1822
  }
1823
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1824
  $empty = empty($view->result);
1825
  foreach ($area_handlers as $area) {
1826
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1827
      return TRUE;
1828
    }
1829
  }
1830
  return FALSE;
1831
}
1832

    
1833
/**
1834
 * This is the entry function. Just gets the form for the current step.
1835
 * The form is always assumed to be multistep, even if it has only one
1836
 * step (the default 'views_form_views_form' step). That way it is actually
1837
 * possible for modules to have a multistep form if they need to.
1838
 */
1839
function views_form($form, &$form_state, $view, $output) {
1840
  $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';
1841
  // Cache the built form to prevent it from being rebuilt prior to validation
1842
  // and submission, which could lead to data being processed incorrectly,
1843
  // because the views rows (and thus, the form elements as well) have changed
1844
  // in the meantime.
1845
  $form_state['cache'] = TRUE;
1846

    
1847
  $form = array();
1848
  $query = drupal_get_query_parameters($_GET, array('q'));
1849
  $form['#action'] = url($view->get_url(), array('query' => $query));
1850
  // Tell the preprocessor whether it should hide the header, footer, pager...
1851
  $form['show_view_elements'] = array(
1852
    '#type' => 'value',
1853
    '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
1854
  );
1855

    
1856
  $form = $form_state['step']($form, $form_state, $view, $output);
1857
  return $form;
1858
}
1859

    
1860
/**
1861
 * Callback for the main step of a Views form.
1862
 * Invoked by views_form().
1863
 */
1864
function views_form_views_form($form, &$form_state, $view, $output) {
1865
  $form['#prefix'] = '<div class="views-form">';
1866
  $form['#suffix'] = '</div>';
1867
  $form['#theme'] = 'views_form_views_form';
1868
  $form['#validate'][] = 'views_form_views_form_validate';
1869
  $form['#submit'][] = 'views_form_views_form_submit';
1870

    
1871
  // Add the output markup to the form array so that it's included when the form
1872
  // array is passed to the theme function.
1873
  $form['output'] = array(
1874
    '#type' => 'markup',
1875
    '#markup' => $output,
1876
    // This way any additional form elements will go before the view
1877
    // (below the exposed widgets).
1878
    '#weight' => 50,
1879
  );
1880

    
1881
  $substitutions = array();
1882
  foreach ($view->field as $field_name => $field) {
1883
    $form_element_name = $field_name;
1884
    if (method_exists($field, 'form_element_name')) {
1885
      $form_element_name = $field->form_element_name();
1886
    }
1887
    $method_form_element_row_id_exists = FALSE;
1888
    if (method_exists($field, 'form_element_row_id')) {
1889
      $method_form_element_row_id_exists = TRUE;
1890
    }
1891

    
1892
    // If the field provides a views form, allow it to modify the $form array.
1893
    $has_form = FALSE;
1894
    if (property_exists($field, 'views_form_callback')) {
1895
      $callback = $field->views_form_callback;
1896
      $callback($view, $field, $form, $form_state);
1897
      $has_form = TRUE;
1898
    }
1899
    elseif (method_exists($field, 'views_form')) {
1900
      $field->views_form($form, $form_state);
1901
      $has_form = TRUE;
1902
    }
1903

    
1904
    // Build the substitutions array for use in the theme function.
1905
    if ($has_form) {
1906
      foreach ($view->result as $row_id => $row) {
1907
        if ($method_form_element_row_id_exists) {
1908
          $form_element_row_id = $field->form_element_row_id($row_id);
1909
        }
1910
        else {
1911
          $form_element_row_id = $row_id;
1912
        }
1913

    
1914
        $substitutions[] = array(
1915
          'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
1916
          'field_name' => $form_element_name,
1917
          'row_id' => $form_element_row_id,
1918
        );
1919
      }
1920
    }
1921
  }
1922

    
1923
  // Give the area handlers a chance to extend the form.
1924
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1925
  $empty = empty($view->result);
1926
  foreach ($area_handlers as $area) {
1927
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1928
      $area->views_form($form, $form_state);
1929
    }
1930
  }
1931

    
1932
  $form['#substitutions'] = array(
1933
    '#type' => 'value',
1934
    '#value' => $substitutions,
1935
  );
1936
  $form['actions'] = array(
1937
    '#type' => 'container',
1938
    '#attributes' => array('class' => array('form-actions')),
1939
    '#weight' => 100,
1940
  );
1941
  $form['actions']['submit'] = array(
1942
    '#type' => 'submit',
1943
    '#value' => t('Save'),
1944
  );
1945

    
1946
  return $form;
1947
}
1948

    
1949
/**
1950
 * Validate handler for the first step of the views form.
1951
 * Calls any existing views_form_validate functions located
1952
 * on the views fields.
1953
 */
1954
function views_form_views_form_validate($form, &$form_state) {
1955
  $view = $form_state['build_info']['args'][0];
1956

    
1957
  // Call the validation method on every field handler that has it.
1958
  foreach ($view->field as $field_name => $field) {
1959
    if (method_exists($field, 'views_form_validate')) {
1960
      $field->views_form_validate($form, $form_state);
1961
    }
1962
  }
1963

    
1964
  // Call the validate method on every area handler that has it.
1965
  foreach (array('header', 'footer') as $area) {
1966
    foreach ($view->{$area} as $area_name => $area_handler) {
1967
      if (method_exists($area_handler, 'views_form_validate')) {
1968
        $area_handler->views_form_validate($form, $form_state);
1969
      }
1970
    }
1971
  }
1972
}
1973

    
1974
/**
1975
 * Submit handler for the first step of the views form.
1976
 * Calls any existing views_form_submit functions located
1977
 * on the views fields.
1978
 */
1979
function views_form_views_form_submit($form, &$form_state) {
1980
  $view = $form_state['build_info']['args'][0];
1981

    
1982
  // Call the submit method on every field handler that has it.
1983
  foreach ($view->field as $field_name => $field) {
1984
    if (method_exists($field, 'views_form_submit')) {
1985
      $field->views_form_submit($form, $form_state);
1986
    }
1987
  }
1988

    
1989
  // Call the submit method on every area handler that has it.
1990
  foreach (array('header', 'footer') as $area) {
1991
    foreach ($view->{$area} as $area_name => $area_handler) {
1992
      if (method_exists($area_handler, 'views_form_submit')) {
1993
        $area_handler->views_form_submit($form, $form_state);
1994
      }
1995
    }
1996
  }
1997
}
1998

    
1999
// ------------------------------------------------------------------
2000
// Exposed widgets form
2001

    
2002
/**
2003
 * Form builder for the exposed widgets form.
2004
 *
2005
 * Be sure that $view and $display are references.
2006
 */
2007
function views_exposed_form($form, &$form_state) {
2008
  // Don't show the form when batch operations are in progress.
2009
  if ($batch = batch_get() && isset($batch['current_set'])) {
2010
    return array(
2011
      // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
2012
      '#theme' => '',
2013
    );
2014
  }
2015

    
2016
  // Make sure that we validate because this form might be submitted
2017
  // multiple times per page.
2018
  $form_state['must_validate'] = TRUE;
2019
  $view = &$form_state['view'];
2020
  $display = &$form_state['display'];
2021

    
2022
  $form_state['input'] = $view->get_exposed_input();
2023

    
2024
  // Let form plugins know this is for exposed widgets.
2025
  $form_state['exposed'] = TRUE;
2026
  // Check if the form was already created
2027
  if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
2028
    return $cache;
2029
  }
2030

    
2031
  $form['#info'] = array();
2032

    
2033
  if (!variable_get('clean_url', FALSE)) {
2034
    $form['q'] = array(
2035
      '#type' => 'hidden',
2036
      '#value' => $view->get_url(),
2037
    );
2038
  }
2039

    
2040
  // Go through each handler and let it generate its exposed widget.
2041
  foreach ($view->display_handler->handlers as $type => $value) {
2042
    foreach ($view->$type as $id => $handler) {
2043
      if ($handler->can_expose() && $handler->is_exposed()) {
2044
        // Grouped exposed filters have their own forms.
2045
        // Instead of render the standard exposed form, a new Select or
2046
        // Radio form field is rendered with the available groups.
2047
        // When an user choose an option the selected value is split
2048
        // into the operator and value that the item represents.
2049
        if ($handler->is_a_group()) {
2050
          $handler->group_form($form, $form_state);
2051
          $id = $handler->options['group_info']['identifier'];
2052
        }
2053
        else {
2054
          $handler->exposed_form($form, $form_state);
2055
        }
2056
        if ($info = $handler->exposed_info()) {
2057
          $form['#info']["$type-$id"] = $info;
2058
        }
2059
      }
2060
    }
2061
  }
2062

    
2063
  $form['submit'] = array(
2064
    '#name' => '', // prevent from showing up in $_GET.
2065
    '#type' => 'submit',
2066
    '#value' => t('Apply'),
2067
    '#id' => drupal_html_id('edit-submit-' . $view->name),
2068
  );
2069

    
2070
  $form['#action'] = url($view->display_handler->get_url());
2071
  $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
2072
  $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
2073
//  $form['#attributes']['class'] = array('views-exposed-form');
2074

    
2075
  // If using AJAX, we need the form plugin.
2076
  if ($view->use_ajax) {
2077
    drupal_add_library('system', 'jquery.form');
2078
  }
2079
  ctools_include('dependent');
2080

    
2081
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2082
  $exposed_form_plugin->exposed_form_alter($form, $form_state);
2083

    
2084
  // Save the form
2085
  views_exposed_form_cache($view->name, $view->current_display, $form);
2086

    
2087
  return $form;
2088
}
2089

    
2090
/**
2091
 * Implement hook_form_alter for the exposed form.
2092
 *
2093
 * Since the exposed form is a GET form, we don't want it to send a wide
2094
 * variety of information.
2095
 */
2096
function views_form_views_exposed_form_alter(&$form, &$form_state) {
2097
  $form['form_build_id']['#access'] = FALSE;
2098
  $form['form_token']['#access'] = FALSE;
2099
  $form['form_id']['#access'] = FALSE;
2100
}
2101

    
2102
/**
2103
 * Validate handler for exposed filters
2104
 */
2105
function views_exposed_form_validate(&$form, &$form_state) {
2106
  foreach (array('field', 'filter') as $type) {
2107
    $handlers = &$form_state['view']->$type;
2108
    foreach ($handlers as $key => $handler) {
2109
      $handlers[$key]->exposed_validate($form, $form_state);
2110
    }
2111
  }
2112
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2113
  $exposed_form_plugin->exposed_form_validate($form, $form_state);
2114
}
2115

    
2116
/**
2117
 * Submit handler for exposed filters
2118
 */
2119
function views_exposed_form_submit(&$form, &$form_state) {
2120
  foreach (array('field', 'filter') as $type) {
2121
    $handlers = &$form_state['view']->$type;
2122
    foreach ($handlers as $key => $info) {
2123
      $handlers[$key]->exposed_submit($form, $form_state);
2124
    }
2125
  }
2126
  $form_state['view']->exposed_data = $form_state['values'];
2127
  $form_state['view']->exposed_raw_input = array();
2128

    
2129

    
2130
  $exclude = array('q', 'submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
2131
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2132
  $exposed_form_plugin->exposed_form_submit($form, $form_state, $exclude);
2133

    
2134
  foreach ($form_state['values'] as $key => $value) {
2135
    if (!in_array($key, $exclude)) {
2136
      $form_state['view']->exposed_raw_input[$key] = $value;
2137
    }
2138
  }
2139
}
2140

    
2141
/**
2142
 * Save the Views exposed form for later use.
2143
 *
2144
 * @param $views_name
2145
 *   String. The views name.
2146
 * @param $display_name
2147
 *   String. The current view display name.
2148
 * @param $form_output
2149
 *   Array (optional). The form structure. Only needed when inserting the value.
2150
 * @return
2151
 *   Array. The form structure, if any. Otherwise, return FALSE.
2152
 */
2153
function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
2154
  // When running tests for exposed filters, this cache should
2155
  // be cleared between each test.
2156
  $views_exposed = &drupal_static(__FUNCTION__);
2157

    
2158
  // Save the form output
2159
  if (!empty($form_output)) {
2160
    $views_exposed[$views_name][$display_name] = $form_output;
2161
    return;
2162
  }
2163

    
2164
  // Return the form output, if any
2165
  return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
2166
}
2167

    
2168
// ------------------------------------------------------------------
2169
// Misc helpers
2170

    
2171
/**
2172
 * Build a list of theme function names for use most everywhere.
2173
 */
2174
function views_theme_functions($hook, $view, $display = NULL) {
2175
  require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'views') . "/theme/theme.inc";
2176
  return _views_theme_functions($hook, $view, $display);
2177
}
2178

    
2179
/**
2180
 * Substitute current time; this works with cached queries.
2181
 */
2182
function views_views_query_substitutions($view) {
2183
  global $language_content;
2184
  return array(
2185
    '***CURRENT_VERSION***' => VERSION,
2186
    '***CURRENT_TIME***' => REQUEST_TIME,
2187
    '***CURRENT_LANGUAGE***' => $language_content->language,
2188
    '***DEFAULT_LANGUAGE***' => language_default('language'),
2189
  );
2190
}
2191

    
2192
/**
2193
 * Implements hook_query_TAG_alter().
2194
 *
2195
 * This is the hook_query_alter() for queries tagged by Views and is used to
2196
 * add in substitutions from hook_views_query_substitutions().
2197
 */
2198
function views_query_views_alter(QueryAlterableInterface $query) {
2199
  $substitutions = $query->getMetaData('views_substitutions');
2200
  $tables =& $query->getTables();
2201
  $where =& $query->conditions();
2202

    
2203
  // Replaces substitions in tables.
2204
  foreach ($tables as $table_name => $table_metadata) {
2205
    foreach ($table_metadata['arguments'] as $replacement_key => $value) {
2206
      if (isset($substitutions[$value])) {
2207
        $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
2208
      }
2209
    }
2210
  }
2211

    
2212
  // Replaces substitions in filter criterias.
2213
  _views_query_tag_alter_condition($query, $where, $substitutions);
2214
}
2215

    
2216
/**
2217
 * Replaces the substitutions recursive foreach condition.
2218
 */
2219
function _views_query_tag_alter_condition(QueryAlterableInterface $query, &$conditions, $substitutions) {
2220
  foreach ($conditions as $condition_id => &$condition) {
2221
    if (is_numeric($condition_id)) {
2222
      if (is_string($condition['field'])) {
2223
        $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
2224
      }
2225
      elseif (is_object($condition['field'])) {
2226
        $sub_conditions =& $condition['field']->conditions();
2227
        _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
2228
      }
2229
      // $condition['value'] is a subquery so alter the subquery recursive.
2230
      // Therefore take sure to get the metadata of the main query.
2231
      if (is_object($condition['value'])) {
2232
        $subquery = $condition['value'];
2233
        $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
2234
        views_query_views_alter($condition['value']);
2235
      }
2236
      elseif (isset($condition['value'])) {
2237
        $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
2238
      }
2239
    }
2240
  }
2241
}
2242

    
2243
/**
2244
 * Embed a view using a PHP snippet.
2245
 *
2246
 * This function is meant to be called from PHP snippets, should one wish to
2247
 * embed a view in a node or something. It's meant to provide the simplest
2248
 * solution and doesn't really offer a lot of options, but breaking the function
2249
 * apart is pretty easy, and this provides a worthwhile guide to doing so.
2250
 *
2251
 * Note that this function does NOT display the title of the view. If you want
2252
 * to do that, you will need to do what this function does manually, by
2253
 * loading the view, getting the preview and then getting $view->get_title().
2254
 *
2255
 * @param $name
2256
 *   The name of the view to embed.
2257
 * @param $display_id
2258
 *   The display id to embed. If unsure, use 'default', as it will always be
2259
 *   valid. But things like 'page' or 'block' should work here.
2260
 * @param ...
2261
 *   Any additional parameters will be passed as arguments.
2262
 */
2263
function views_embed_view($name, $display_id = 'default') {
2264
  $args = func_get_args();
2265
  array_shift($args); // remove $name
2266
  if (count($args)) {
2267
    array_shift($args); // remove $display_id
2268
  }
2269

    
2270
  $view = views_get_view($name);
2271
  if (!$view || !$view->access($display_id)) {
2272
    return;
2273
  }
2274

    
2275
  return $view->preview($display_id, $args);
2276
}
2277

    
2278
/**
2279
 * Get the result of a view.
2280
 *
2281
 * @param string $name
2282
 *   The name of the view to retrieve the data from.
2283
 * @param string $display_id
2284
 *   The display id. On the edit page for the view in question, you'll find
2285
 *   a list of displays at the left side of the control area. "Master"
2286
 *   will be at the top of that list. Hover your cursor over the name of the
2287
 *   display you want to use. An URL will appear in the status bar of your
2288
 *   browser. This is usually at the bottom of the window, in the chrome.
2289
 *   Everything after #views-tab- is the display ID, e.g. page_1.
2290
 * @param ...
2291
 *   Any additional parameters will be passed as arguments.
2292
 * @return array
2293
 *   An array containing an object for each view item.
2294
 */
2295
function views_get_view_result($name, $display_id = NULL) {
2296
  $args = func_get_args();
2297
  array_shift($args); // remove $name
2298
  if (count($args)) {
2299
    array_shift($args); // remove $display_id
2300
  }
2301

    
2302
  $view = views_get_view($name);
2303
  if (is_object($view)) {
2304
    if (is_array($args)) {
2305
      $view->set_arguments($args);
2306
    }
2307
    if (is_string($display_id)) {
2308
      $view->set_display($display_id);
2309
    }
2310
    else {
2311
      $view->init_display();
2312
    }
2313
    $view->pre_execute();
2314
    $view->execute();
2315
    return $view->result;
2316
  }
2317
  else {
2318
    return array();
2319
  }
2320
}
2321

    
2322
/**
2323
 * Export a field.
2324
 */
2325
function views_var_export($var, $prefix = '', $init = TRUE) {
2326
  if (is_array($var)) {
2327
    if (empty($var)) {
2328
      $output = 'array()';
2329
    }
2330
    else {
2331
      $output = "array(\n";
2332
      foreach ($var as $key => $value) {
2333
        $output .= "  " . views_var_export($key, '', FALSE) . " => " . views_var_export($value, '  ', FALSE) . ",\n";
2334
      }
2335
      $output .= ')';
2336
    }
2337
  }
2338
  elseif (is_bool($var)) {
2339
    $output = $var ? 'TRUE' : 'FALSE';
2340
  }
2341
  elseif (is_string($var) && strpos($var, "\n") !== FALSE) {
2342
    // Replace line breaks in strings with a token for replacement
2343
    // at the very end. This protects multi-line strings from
2344
    // unintentional indentation.
2345
    $var = str_replace("\n", "***BREAK***", $var);
2346
    $output = var_export($var, TRUE);
2347
  }
2348
  else {
2349
    $output = var_export($var, TRUE);
2350
  }
2351

    
2352
  if ($prefix) {
2353
    $output = str_replace("\n", "\n$prefix", $output);
2354
  }
2355

    
2356
  if ($init) {
2357
    $output = str_replace("***BREAK***", "\n", $output);
2358
  }
2359

    
2360
  return $output;
2361
}
2362

    
2363
/**
2364
 * Prepare a string for use as a valid CSS identifier (element, class or ID name).
2365
 * This function is similar to a core version but with more sane filter values.
2366
 *
2367
 * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
2368
 * CSS identifiers (including element names, classes, and IDs in selectors.)
2369
 *
2370
 * @param $identifier
2371
 *   The identifier to clean.
2372
 * @param $filter
2373
 *   An array of string replacements to use on the identifier.
2374
 * @return
2375
 *   The cleaned identifier.
2376
 *
2377
 * @see drupal_clean_css_identifier()
2378
 */
2379
function views_clean_css_identifier($identifier, $filter = array(' ' => '-', '/' => '-', '[' => '-', ']' => '')) {
2380
  // By default, we filter using Drupal's coding standards.
2381
  $identifier = strtr($identifier, $filter);
2382

    
2383
  // Valid characters in a CSS identifier are:
2384
  // - the hyphen (U+002D)
2385
  // - a-z (U+0030 - U+0039)
2386
  // - A-Z (U+0041 - U+005A)
2387
  // - the underscore (U+005F)
2388
  // - 0-9 (U+0061 - U+007A)
2389
  // - ISO 10646 characters U+00A1 and higher
2390
  // We strip out any character not in the above list.
2391
  $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);
2392

    
2393
  return $identifier;
2394
}
2395

    
2396
/**
2397
 * Implement hook_views_exportables().
2398
 */
2399
function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
2400
  $all_views = views_get_all_views();
2401
  if ($op == 'list') {
2402

    
2403
    foreach ($all_views as $name => $view) {
2404
      // in list, $views is a list of tags.
2405
      if (empty($views) || in_array($view->tag, $views)) {
2406
        $return[$name] = array(
2407
          'name' => check_plain($name),
2408
          'desc' => check_plain($view->description),
2409
          'tag' => check_plain($view->tag)
2410
        );
2411
      }
2412
    }
2413
    return $return;
2414
  }
2415

    
2416
  if ($op == 'export') {
2417
    $code = "/**\n";
2418
    $code .= " * Implement hook_views_default_views().\n";
2419
    $code .= " */\n";
2420
    $code .= "function " . $name . "_views_default_views() {\n";
2421
    foreach ($views as $view => $truth) {
2422
      $code .= "  /*\n";
2423
      $code .= "   * View " . var_export($all_views[$view]->name, TRUE) . "\n";
2424
      $code .= "   */\n";
2425
      $code .= $all_views[$view]->export('  ');
2426
      $code .= '  $views[$view->name] = $view;' . "\n\n";
2427
    }
2428
    $code .= "  return \$views;\n";
2429
    $code .= "}\n";
2430

    
2431
    return $code;
2432
  }
2433
}
2434

    
2435
/**
2436
 * #process callback to see if we need to check_plain() the options.
2437
 *
2438
 * Since FAPI is inconsistent, the #options are sanitized for you in all cases
2439
 * _except_ checkboxes. We have form elements that are sometimes 'select' and
2440
 * sometimes 'checkboxes', so we need decide late in the form rendering cycle
2441
 * if the options need to be sanitized before they're rendered. This callback
2442
 * inspects the type, and if it's still 'checkboxes', does the sanitation.
2443
 */
2444
function views_process_check_options($element, &$form_state) {
2445
  if ($element['#type'] == 'checkboxes' || $element['#type'] == 'checkbox') {
2446
    $element['#options'] = array_map('check_plain', $element['#options']);
2447
  }
2448
  return $element;
2449
}
2450

    
2451
/**
2452
 * Trim the field down to the specified length.
2453
 *
2454
 * @param $alter
2455
 *   - max_length: Maximum length of the string, the rest gets truncated.
2456
 *   - word_boundary: Trim only on a word boundary.
2457
 *   - ellipsis: Show an ellipsis (...) at the end of the trimmed string.
2458
 *   - html: Take sure that the html is correct.
2459
 *
2460
 * @param $value
2461
 *   The string which should be trimmed.
2462
 */
2463
function views_trim_text($alter, $value) {
2464
  if (drupal_strlen($value) > $alter['max_length']) {
2465
    $value = drupal_substr($value, 0, $alter['max_length']);
2466
    // TODO: replace this with cleanstring of ctools
2467
    if (!empty($alter['word_boundary'])) {
2468
      $regex = "(.*)\b.+";
2469
      if (function_exists('mb_ereg')) {
2470
        mb_regex_encoding('UTF-8');
2471
        $found = mb_ereg($regex, $value, $matches);
2472
      }
2473
      else {
2474
        $found = preg_match("/$regex/us", $value, $matches);
2475
      }
2476
      if ($found) {
2477
        $value = $matches[1];
2478
      }
2479
    }
2480
    // Remove scraps of HTML entities from the end of a strings
2481
    $value = rtrim(preg_replace('/(?:<(?!.+>)|&(?!.+;)).*$/us', '', $value));
2482

    
2483
    if (!empty($alter['ellipsis'])) {
2484
      $value .= t('...');
2485
    }
2486
  }
2487
  if (!empty($alter['html'])) {
2488
    $value = _filter_htmlcorrector($value);
2489
  }
2490

    
2491
  return $value;
2492
}
2493

    
2494
/**
2495
 * Adds one to each key of the array.
2496
 *
2497
 * For example array(0 => 'foo') would be array(1 => 'foo').
2498
 */
2499
function views_array_key_plus($array) {
2500
  $keys = array_keys($array);
2501
  rsort($keys);
2502
  foreach ($keys as $key) {
2503
    $array[$key+1] = $array[$key];
2504
    unset($array[$key]);
2505
  }
2506
  asort($array);
2507
  return $array;
2508
}
2509

    
2510
/**
2511
 * Report to CTools that we use hook_views_api instead of hook_ctools_plugin_api()
2512
 */
2513
function views_ctools_plugin_api_hook_name() {
2514
  return 'views_api';
2515
}
2516

    
2517
// Declare API compatibility on behalf of core modules:
2518

    
2519
/**
2520
 * Implements hook_views_api().
2521
 *
2522
 * This one is used as the base to reduce errors when updating.
2523
 */
2524
function views_views_api() {
2525
  return array(
2526
    // in your modules do *not* use views_api_version()!!!
2527
    'api' => views_api_version(),
2528
    'path' => drupal_get_path('module', 'views') . '/modules',
2529
  );
2530
}
2531

    
2532
if (!function_exists('aggregator_views_api')) {
2533
  function aggregator_views_api() { return views_views_api(); }
2534
}
2535

    
2536
if (!function_exists('book_views_api')) {
2537
  function book_views_api() { return views_views_api(); }
2538
}
2539

    
2540
if (!function_exists('comment_views_api')) {
2541
  function comment_views_api() { return views_views_api(); }
2542
}
2543

    
2544
if (!function_exists('field_views_api')) {
2545
  function field_views_api() { return views_views_api(); }
2546
}
2547

    
2548
if (!function_exists('file_views_api')) {
2549
  function file_views_api() { return views_views_api(); }
2550
}
2551

    
2552
if (!function_exists('filter_views_api')) {
2553
  function filter_views_api() { return views_views_api(); }
2554
}
2555

    
2556
if (!function_exists('image_views_api')) {
2557
  function image_views_api() { return views_views_api(); }
2558
}
2559

    
2560
if (!function_exists('locale_views_api')) {
2561
  function locale_views_api() { return views_views_api(); }
2562
}
2563

    
2564
if (!function_exists('node_views_api')) {
2565
  function node_views_api() { return views_views_api(); }
2566
}
2567

    
2568
if (!function_exists('poll_views_api')) {
2569
  function poll_views_api() { return views_views_api(); }
2570
}
2571

    
2572
if (!function_exists('profile_views_api')) {
2573
  function profile_views_api() { return views_views_api(); }
2574
}
2575

    
2576
if (!function_exists('search_views_api')) {
2577
  function search_views_api() { return views_views_api(); }
2578
}
2579

    
2580
if (!function_exists('statistics_views_api')) {
2581
  function statistics_views_api() { return views_views_api(); }
2582
}
2583

    
2584
if (!function_exists('system_views_api')) {
2585
  function system_views_api() { return views_views_api(); }
2586
}
2587

    
2588
if (!function_exists('tracker_views_api')) {
2589
  function tracker_views_api() { return views_views_api(); }
2590
}
2591

    
2592
if (!function_exists('taxonomy_views_api')) {
2593
  function taxonomy_views_api() { return views_views_api(); }
2594
}
2595

    
2596
if (!function_exists('translation_views_api')) {
2597
  function translation_views_api() { return views_views_api(); }
2598
}
2599

    
2600
if (!function_exists('user_views_api')) {
2601
  function user_views_api() { return views_views_api(); }
2602
}
2603

    
2604
if (!function_exists('contact_views_api')) {
2605
  function contact_views_api() { return views_views_api(); }
2606
}