Projet

Général

Profil

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

root / drupal7 / sites / all / modules / views / views.module @ 7547bb19

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
 * Advertise the current views api version
14
 */
15
function views_api_version() {
16
  return '3.0';
17
}
18

    
19
/**
20
 * Implements hook_forms().
21
 *
22
 * To provide distinct form IDs for Views forms, the View name and
23
 * specific display name are appended to the base ID,
24
 * views_form_views_form. When such a form is built or submitted, this
25
 * function will return the proper callback function to use for the given form.
26
 */
27
function views_forms($form_id, $args) {
28
  if (strpos($form_id, 'views_form_') === 0) {
29
    return array(
30
      $form_id => array(
31
        'callback' => 'views_form',
32
      ),
33
    );
34
  }
35
}
36

    
37
/**
38
 * Returns a form ID for a Views form using the name and display of the View.
39
 */
40
function views_form_id($view) {
41
  $parts = array(
42
    'views_form',
43
    $view->name,
44
    $view->current_display,
45
  );
46

    
47
  return implode('_', $parts);
48
}
49

    
50
/**
51
 * Views will not load plugins advertising a version older than this.
52
 */
53
function views_api_minimum_version() {
54
  return '2';
55
}
56

    
57
/**
58
 * Implement hook_theme(). Register views theming functions.
59
 */
60
function views_theme($existing, $type, $theme, $path) {
61
  $path = drupal_get_path('module', 'views');
62
  ctools_include('theme', 'views', 'theme');
63

    
64
  // Some quasi clever array merging here.
65
  $base = array(
66
    'file' => 'theme.inc',
67
    'path' => $path . '/theme',
68
  );
69

    
70
  // Our extra version of pager from pager.inc
71
  $hooks['views_mini_pager'] = $base + array(
72
    'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array()),
73
    'pattern' => 'views_mini_pager__',
74
  );
75

    
76
  $variables = array(
77
    // For displays, we pass in a dummy array as the first parameter, since
78
    // $view is an object but the core contextual_preprocess() function only
79
    // attaches contextual links when the primary theme argument is an array.
80
    'display' => array('view_array' => array(), 'view' => NULL),
81
    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
82
    'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
83
    'exposed_form' => array('view' => NULL, 'options' => NULL),
84
    'pager' => array(
85
      'view' => NULL, 'options' => NULL,
86
      'tags' => array(), 'quantity' => 10, 'element' => 0, 'parameters' => array()
87
    ),
88
  );
89

    
90
  // Default view themes
91
  $hooks['views_view_field'] = $base + array(
92
    'pattern' => 'views_view_field__',
93
    'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
94
  );
95
  $hooks['views_view_grouping'] = $base + array(
96
    'pattern' => 'views_view_grouping__',
97
    'variables' => array('view' => NULL, 'grouping' => NULL, 'grouping_level' => NULL, 'rows' => NULL, 'title' => NULL),
98
  );
99

    
100
  $plugins = views_fetch_plugin_data();
101

    
102
  // Register theme functions for all style plugins
103
  foreach ($plugins as $type => $info) {
104
    foreach ($info as $plugin => $def) {
105
      if (isset($def['theme']) && (!isset($def['register theme']) || !empty($def['register theme']))) {
106
        $hooks[$def['theme']] = array(
107
          'pattern' => $def['theme'] . '__',
108
          'file' => $def['theme file'],
109
          'path' => $def['theme path'],
110
          'variables' => $variables[$type],
111
        );
112

    
113
        $include = DRUPAL_ROOT . '/' . $def['theme path'] . '/' . $def['theme file'];
114
        if (file_exists($include)) {
115
          require_once $include;
116
        }
117

    
118
        if (!function_exists('theme_' . $def['theme'])) {
119
          $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
120
        }
121
      }
122
      if (isset($def['additional themes'])) {
123
        foreach ($def['additional themes'] as $theme => $theme_type) {
124
          if (empty($theme_type)) {
125
            $theme = $theme_type;
126
            $theme_type = $type;
127
          }
128

    
129
          $hooks[$theme] = array(
130
            'pattern' => $theme . '__',
131
            'file' => $def['theme file'],
132
            'path' => $def['theme path'],
133
            'variables' => $variables[$theme_type],
134
          );
135

    
136
          if (!function_exists('theme_' . $theme)) {
137
            $hooks[$theme]['template'] = drupal_clean_css_identifier($theme);
138
          }
139
        }
140
      }
141
    }
142
  }
143

    
144
  $hooks['views_form_views_form'] = $base + array(
145
    'render element' => 'form',
146
  );
147

    
148
  $hooks['views_exposed_form'] = $base + array(
149
    'template' => 'views-exposed-form',
150
    'pattern' => 'views_exposed_form__',
151
    'render element' => 'form',
152
  );
153

    
154
  $hooks['views_more'] = $base + array(
155
    'template' => 'views-more',
156
    'pattern' => 'views_more__',
157
    'variables' => array('more_url' => NULL, 'link_text' => 'more', 'view' => NULL),
158
  );
159

    
160
  // Add theme suggestions which are part of modules.
161
  foreach (views_get_module_apis() as $info) {
162
    if (isset($info['template path'])) {
163
      $hooks += _views_find_module_templates($hooks, $info['template path']);
164
    }
165
  }
166
  return $hooks;
167
}
168

    
169
/**
170
 * Scans a directory of a module for template files.
171
 *
172
 * @param $cache
173
 *   The existing cache of theme hooks to test against.
174
 * @param $path
175
 *   The path to search.
176
 *
177
 * @see drupal_find_theme_templates()
178
 */
179
function _views_find_module_templates($cache, $path) {
180
  $templates = array();
181
  $regex = '/' . '\.tpl\.php' . '$' . '/';
182

    
183
  // Because drupal_system_listing works the way it does, we check for real
184
  // templates separately from checking for patterns.
185
  $files = drupal_system_listing($regex, $path, 'name', 0);
186
  foreach ($files as $template => $file) {
187
    // Chop off the remaining extensions if there are any. $template already
188
    // has the rightmost extension removed, but there might still be more,
189
    // such as with .tpl.php, which still has .tpl in $template at this point.
190
    if (($pos = strpos($template, '.')) !== FALSE) {
191
      $template = substr($template, 0, $pos);
192
    }
193
    // Transform - in filenames to _ to match function naming scheme
194
    // for the purposes of searching.
195
    $hook = strtr($template, '-', '_');
196
    if (isset($cache[$hook])) {
197
      $templates[$hook] = array(
198
        'template' => $template,
199
        'path' => dirname($file->filename),
200
        'includes' => isset($cache[$hook]['includes']) ? $cache[$hook]['includes'] : NULL,
201
      );
202
    }
203
    // Ensure that the pattern is maintained from base themes to its sub-themes.
204
    // Each sub-theme will have their templates scanned so the pattern must be
205
    // held for subsequent runs.
206
    if (isset($cache[$hook]['pattern'])) {
207
      $templates[$hook]['pattern'] = $cache[$hook]['pattern'];
208
    }
209
  }
210

    
211
  $patterns = array_keys($files);
212

    
213
  foreach ($cache as $hook => $info) {
214
    if (!empty($info['pattern'])) {
215
      // Transform _ in pattern to - to match file naming scheme
216
      // for the purposes of searching.
217
      $pattern = strtr($info['pattern'], '_', '-');
218

    
219
      $matches = preg_grep('/^'. $pattern .'/', $patterns);
220
      if ($matches) {
221
        foreach ($matches as $match) {
222
          $file = substr($match, 0, strpos($match, '.'));
223
          // Put the underscores back in for the hook name and register this pattern.
224
          $templates[strtr($file, '-', '_')] = array(
225
            'template' => $file,
226
            'path' => dirname($files[$match]->uri),
227
            'variables' => isset($info['variables']) ? $info['variables'] : NULL,
228
            'render element' => isset($info['render element']) ? $info['render element'] : NULL,
229
            'base hook' => $hook,
230
            'includes' => isset($info['includes']) ? $info['includes'] : NULL,
231
          );
232
        }
233
      }
234
    }
235
  }
236

    
237
  return $templates;
238
}
239

    
240
/**
241
 * Returns a list of plugins and metadata about them.
242
 *
243
 * @return array
244
 *   An array keyed by PLUGIN_TYPE:PLUGIN_NAME, like 'display:page' or
245
 *   'pager:full', containing an array with the following keys:
246
 *   - title: The plugin's title.
247
 *   - type: The plugin type.
248
 *   - module: The module providing the plugin.
249
 *   - views: An array of enabled Views that are currently using this plugin,
250
 *     keyed by machine name.
251
 */
252
function views_plugin_list() {
253
  $plugin_data = views_fetch_plugin_data();
254
  $plugins = array();
255
  foreach (views_get_enabled_views() as $view) {
256
    foreach ($view->display as $display_id => $display) {
257
      foreach ($plugin_data as $type => $info) {
258
        if ($type == 'display' && isset($display->display_plugin)) {
259
          $name = $display->display_plugin;
260
        }
261
        elseif (isset($display->display_options["{$type}_plugin"])) {
262
          $name = $display->display_options["{$type}_plugin"];
263
        }
264
        elseif (isset($display->display_options[$type]['type'])) {
265
          $name = $display->display_options[$type]['type'];
266
        }
267
        else {
268
          continue;
269
        }
270

    
271
        // Key first by the plugin type, then the name.
272
        $key = $type . ':' . $name;
273
        // Add info for this plugin.
274
        if (!isset($plugins[$key])) {
275
          $plugins[$key] = array(
276
            'type' => $type,
277
            'title' => check_plain($info[$name]['title']),
278
            'module' => check_plain($info[$name]['module']),
279
            'views' => array(),
280
          );
281
        }
282

    
283
        // Add this view to the list for this plugin.
284
        $plugins[$key]['views'][$view->name] = $view->name;
285
      }
286
    }
287
  }
288
  return $plugins;
289
}
290

    
291
/**
292
 * A theme preprocess function to automatically allow view-based node
293
 * templates if called from a view.
294
 *
295
 * The 'modules/node.views.inc' file is a better place for this, but
296
 * we haven't got a chance to load that file before Drupal builds the
297
 * node portion of the theme registry.
298
 */
299
function views_preprocess_node(&$vars) {
300
  // The 'view' attribute of the node is added in views_preprocess_node()
301
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
302
    $vars['view'] = $vars['node']->view;
303
    $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name;
304
    if (!empty($vars['node']->view->current_display)) {
305
      $vars['theme_hook_suggestions'][] = 'node__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
306

    
307
      // If a node is being rendered in a view, and the view does not have a path,
308
      // prevent drupal from accidentally setting the $page variable:
309
      if ($vars['page'] && $vars['view_mode'] == 'full' && !$vars['view']->display_handler->has_path()) {
310
        $vars['page'] = FALSE;
311
      }
312
    }
313
  }
314

    
315
  // Allow to alter comments and links based on the settings in the row plugin.
316
  if (!empty($vars['view']->style_plugin->row_plugin) && get_class($vars['view']->style_plugin->row_plugin) == 'views_plugin_row_node_view') {
317
    node_row_node_view_preprocess_node($vars);
318
  }
319
}
320

    
321
/**
322
 * A theme preprocess function to automatically allow view-based node
323
 * templates if called from a view.
324
 */
325
function views_preprocess_comment(&$vars) {
326
  // The 'view' attribute of the node is added in template_preprocess_views_view_row_comment()
327
  if (!empty($vars['node']->view) && !empty($vars['node']->view->name)) {
328
    $vars['view'] = &$vars['node']->view;
329
    $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name;
330
    if (!empty($vars['node']->view->current_display)) {
331
      $vars['theme_hook_suggestions'][] = 'comment__view__' . $vars['node']->view->name . '__' . $vars['node']->view->current_display;
332
    }
333
  }
334
}
335

    
336
/**
337
 * Implement hook_permission().
338
 */
339
function views_permission() {
340
  return array(
341
    'administer views' => array(
342
      'title' => t('Administer views'),
343
      'description' => t('Access the views administration pages.'),
344
      'restrict access' => TRUE,
345
    ),
346
    'access all views' => array(
347
      'title' => t('Bypass views access control'),
348
      'description' => t('Bypass access control when accessing views.'),
349
      'restrict access' => TRUE,
350
    ),
351
  );
352
}
353

    
354
/**
355
 * Implement hook_menu().
356
 */
357
function views_menu() {
358
  $items = array();
359
  $items['views/ajax'] = array(
360
    'title' => 'Views',
361
    'page callback' => 'views_ajax',
362
    'theme callback' => 'ajax_base_page_theme',
363
    'delivery callback' => 'ajax_deliver',
364
    'access callback' => TRUE,
365
    'description' => 'Ajax callback for view loading.',
366
    'type' => MENU_CALLBACK,
367
    'file' => 'includes/ajax.inc',
368
  );
369
  // Path is not admin/structure/views due to menu complications with the wildcards from
370
  // the generic ajax callback.
371
  $items['admin/views/ajax/autocomplete/user'] = array(
372
    'page callback' => 'views_ajax_autocomplete_user',
373
    'theme callback' => 'ajax_base_page_theme',
374
    'access callback' => 'user_access',
375
    'access arguments' => array('access user profiles'),
376
    'type' => MENU_CALLBACK,
377
    'file' => 'includes/ajax.inc',
378
  );
379
  // Define another taxonomy autocomplete because the default one of drupal
380
  // does not support a vid a argument anymore
381
  $items['admin/views/ajax/autocomplete/taxonomy'] = array(
382
    'page callback' => 'views_ajax_autocomplete_taxonomy',
383
    'theme callback' => 'ajax_base_page_theme',
384
    'access callback' => 'user_access',
385
    'access arguments' => array('access content'),
386
    'type' => MENU_CALLBACK,
387
    'file' => 'includes/ajax.inc',
388
  );
389
  return $items;
390
}
391

    
392
/**
393
 * Implement hook_menu_alter().
394
 */
395
function views_menu_alter(&$callbacks) {
396
  $our_paths = array();
397
  $views = views_get_applicable_views('uses hook menu');
398
  foreach ($views as $data) {
399
    list($view, $display_id) = $data;
400
    $result = $view->execute_hook_menu($display_id, $callbacks);
401
    if (is_array($result)) {
402
      // The menu system doesn't support having two otherwise
403
      // identical paths with different placeholders.  So we
404
      // want to remove the existing items from the menu whose
405
      // paths would conflict with ours.
406

    
407
      // First, we must find any existing menu items that may
408
      // conflict.  We use a regular expression because we don't
409
      // know what placeholders they might use.  Note that we
410
      // first construct the regex itself by replacing %views_arg
411
      // in the display path, then we use this constructed regex
412
      // (which will be something like '#^(foo/%[^/]*/bar)$#') to
413
      // search through the existing paths.
414
      $regex = '#^(' . preg_replace('#%views_arg#', '%[^/]*', implode('|', array_keys($result))) . ')$#';
415
      $matches = preg_grep($regex, array_keys($callbacks));
416

    
417
      // Remove any conflicting items that were found.
418
      foreach ($matches as $path) {
419
        // Don't remove the paths we just added!
420
        if (!isset($our_paths[$path])) {
421
          unset($callbacks[$path]);
422
        }
423
      }
424
      foreach ($result as $path => $item) {
425
        if (!isset($callbacks[$path])) {
426
          // Add a new item, possibly replacing (and thus effectively
427
          // overriding) one that we removed above.
428
          $callbacks[$path] = $item;
429
        }
430
        else {
431
          // This item already exists, so it must be one that we added.
432
          // We change the various callback arguments to pass an array
433
          // of possible display IDs instead of a single ID.
434
          $callbacks[$path]['page arguments'][1] = (array)$callbacks[$path]['page arguments'][1];
435
          $callbacks[$path]['page arguments'][1][] = $display_id;
436
          $callbacks[$path]['access arguments'][] = $item['access arguments'][0];
437
          $callbacks[$path]['load arguments'][1] = (array)$callbacks[$path]['load arguments'][1];
438
          $callbacks[$path]['load arguments'][1][] = $display_id;
439
        }
440
        $our_paths[$path] = TRUE;
441
      }
442
    }
443
  }
444

    
445
  // Save memory: Destroy those views.
446
  foreach ($views as $data) {
447
    list($view, $display_id) = $data;
448
    $view->destroy();
449
  }
450
}
451

    
452
/**
453
 * Helper function for menu loading. This will automatically be
454
 * called in order to 'load' a views argument; primarily it
455
 * will be used to perform validation.
456
 *
457
 * @param $value
458
 *   The actual value passed.
459
 * @param $name
460
 *   The name of the view. This needs to be specified in the 'load function'
461
 *   of the menu entry.
462
 * @param $display_id
463
 *   The display id that will be loaded for this menu item.
464
 * @param $index
465
 *   The menu argument index. This counts from 1.
466
 */
467
function views_arg_load($value, $name, $display_id, $index) {
468
 static $views = array();
469

    
470
  $display_ids = is_array($display_id) ? $display_id : array($display_id);
471
  $display_id = reset($display_ids);
472

    
473
  foreach ($display_ids as $id) {
474
    // Make sure we haven't already loaded this views argument for a similar
475
    // menu item elsewhere. Since access is always checked for the current user,
476
    // we are sure that the static cache contains valid entries.
477
    $key = $name . ':' . $id . ':' . $value . ':' . $index;
478
    if (isset($views[$key])) {
479
      return $views[$key];
480
    }
481
    // Lazy load the view object to avoid unnecessary work.
482
    if (!isset($view)) {
483
      $view = views_get_view($name);
484
    }
485
    // Pick the first display we have access to.
486
    if ($view && count($display_ids) > 1 && $view->access($id)) {
487
      $display_id = $id;
488
      break;
489
    }
490
  }
491

    
492
  if ($view) {
493
    $view->set_display($display_id);
494
    $view->init_handlers();
495

    
496
    $ids = array_keys($view->argument);
497

    
498
    $indexes = array();
499
    $path = explode('/', $view->get_path());
500

    
501
    foreach ($path as $id => $piece) {
502
      if ($piece == '%' && !empty($ids)) {
503
        $indexes[$id] = array_shift($ids);
504
      }
505
    }
506

    
507
    if (isset($indexes[$index])) {
508
      if (isset($view->argument[$indexes[$index]])) {
509
        $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
510
        $view->destroy();
511

    
512
        // Store the output in case we load this same menu item again.
513
        $views[$key] = $arg;
514
        return $arg;
515
      }
516
    }
517
    $view->destroy();
518
  }
519
}
520

    
521
/**
522
 * Page callback: Displays a page view, given a name and display id.
523
 *
524
 * @param $name
525
 *   The name of a view.
526
 * @param $display_id
527
 *   The display id of a view.
528
 *
529
 * @return
530
 *   Either the HTML of a fully-executed view, or MENU_NOT_FOUND.
531
 */
532
function views_page($name, $display_id) {
533
  $args = func_get_args();
534
  // Remove $name and $display_id from the arguments.
535
  array_shift($args);
536
  array_shift($args);
537

    
538
  // Load the view and render it.
539
  if ($view = views_get_view($name)) {
540
    return $view->execute_display($display_id, $args);
541
  }
542

    
543
  // Fallback; if we get here no view was found or handler was not valid.
544
  return MENU_NOT_FOUND;
545
}
546

    
547
/**
548
 * Implements hook_page_alter().
549
 */
550
function views_page_alter(&$page) {
551
  // If the main content of this page contains a view, attach its contextual
552
  // links to the overall page array. This allows them to be rendered directly
553
  // next to the page title.
554
  $view = views_get_page_view();
555
  if (!empty($view)) {
556
    // If a module is still putting in the display like we used to, catch that.
557
    if (is_subclass_of($view, 'views_plugin_display')) {
558
      $view = $view->view;
559
    }
560

    
561
    views_add_contextual_links($page, 'page', $view, $view->current_display);
562
  }
563
}
564

    
565
/**
566
 * Implements MODULE_preprocess_HOOK() for html.tpl.php.
567
 */
568
function views_preprocess_html(&$variables) {
569
  // If the page contains a view as its main content, contextual links may have
570
  // been attached to the page as a whole; for example, by views_page_alter().
571
  // This allows them to be associated with the page and rendered by default
572
  // next to the page title (which we want). However, it also causes the
573
  // Contextual Links module to treat the wrapper for the entire page (i.e.,
574
  // the <body> tag) as the HTML element that these contextual links are
575
  // associated with. This we don't want; for better visual highlighting, we
576
  // prefer a smaller region to be chosen. The region we prefer differs from
577
  // theme to theme and depends on the details of the theme's markup in
578
  // page.tpl.php, so we can only find it using JavaScript. We therefore remove
579
  // the "contextual-links-region" class from the <body> tag here and add
580
  // JavaScript that will insert it back in the correct place.
581
  if (!empty($variables['page']['#views_contextual_links_info'])) {
582
    $key = array_search('contextual-links-region', $variables['classes_array']);
583
    if ($key !== FALSE) {
584
      $variables['classes_array'] = array_diff($variables['classes_array'], array('contextual-links-region'));
585
      // Add the JavaScript, with a group and weight such that it will run
586
      // before modules/contextual/contextual.js.
587
      drupal_add_js(drupal_get_path('module', 'views') . '/js/views-contextual.js', array('group' => JS_LIBRARY, 'weight' => -1));
588
    }
589
  }
590
}
591

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

    
613
/**
614
 * Implements hook_contextual_links_view_alter().
615
 */
616
function views_contextual_links_view_alter(&$element, $items) {
617
  // If we are rendering views-related contextual links attached to the overall
618
  // page array, add a class to the list of contextual links. This will be used
619
  // by the JavaScript added in views_preprocess_html().
620
  if (!empty($element['#element']['#views_contextual_links_info']) && !empty($element['#element']['#type']) && $element['#element']['#type'] == 'page') {
621
    $element['#attributes']['class'][] = 'views-contextual-links-page';
622
  }
623
}
624

    
625
/**
626
 * Implement hook_block_info().
627
 */
628
function views_block_info() {
629
  // Try to avoid instantiating all the views just to get the blocks info.
630
  views_include('cache');
631
  $cache = views_cache_get('views_block_items', TRUE);
632
  if ($cache && is_array($cache->data)) {
633
    return $cache->data;
634
  }
635

    
636
  $items = array();
637
  $views = views_get_all_views();
638
  foreach ($views as $view) {
639
    // disabled views get nothing.
640
    if (!empty($view->disabled)) {
641
      continue;
642
    }
643

    
644
    $view->init_display();
645
    foreach ($view->display as $display_id => $display) {
646

    
647
      if (isset($display->handler) && !empty($display->handler->definition['uses hook block'])) {
648
        $result = $display->handler->execute_hook_block_list();
649
        if (is_array($result)) {
650
          $items = array_merge($items, $result);
651
        }
652
      }
653

    
654
      if (isset($display->handler) && $display->handler->get_option('exposed_block')) {
655
        $result = $display->handler->get_special_blocks();
656
        if (is_array($result)) {
657
          $items = array_merge($items, $result);
658
        }
659
      }
660
    }
661
  }
662

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

    
668
  // get the keys because we're modifying the array and we don't want to
669
  // confuse PHP too much.
670
  $keys = array_keys($items);
671
  foreach ($keys as $delta) {
672
    if (strlen($delta) >= 32) {
673
      $hash = md5($delta);
674
      $hashes[$hash] = $delta;
675
      $items[$hash] = $items[$delta];
676
      unset($items[$delta]);
677
    }
678
  }
679

    
680
  // Only save hashes if they have changed.
681
  $old_hashes = variable_get('views_block_hashes', array());
682
  if ($hashes != $old_hashes) {
683
    variable_set('views_block_hashes', $hashes);
684
  }
685
  // Save memory: Destroy those views.
686
  foreach ($views as $view) {
687
    $view->destroy();
688
  }
689

    
690
  views_cache_set('views_block_items', $items, TRUE);
691

    
692
  return $items;
693
}
694

    
695
/**
696
 * Implement hook_block_view().
697
 */
698
function views_block_view($delta) {
699
  $start = microtime(TRUE);
700
  // if this is 32, this should be an md5 hash.
701
  if (strlen($delta) == 32) {
702
    $hashes = variable_get('views_block_hashes', array());
703
    if (!empty($hashes[$delta])) {
704
      $delta = $hashes[$delta];
705
    }
706
  }
707

    
708
  // This indicates it's a special one.
709
  if (substr($delta, 0, 1) == '-') {
710
    list($nothing, $type, $name, $display_id) = explode('-', $delta);
711
    // Put the - back on.
712
    $type = '-' . $type;
713
    if ($view = views_get_view($name)) {
714
      if ($view->access($display_id)) {
715
        $view->set_display($display_id);
716
        if (isset($view->display_handler)) {
717
          $output = $view->display_handler->view_special_blocks($type);
718
          // Before returning the block output, convert it to a renderable
719
          // array with contextual links.
720
          views_add_block_contextual_links($output, $view, $display_id, 'special_block_' . $type);
721
          $view->destroy();
722
          return $output;
723
        }
724
      }
725
      $view->destroy();
726
    }
727
  }
728

    
729
  // If the delta doesn't contain valid data return nothing.
730
  $explode = explode('-', $delta);
731
  if (count($explode) != 2) {
732
    return;
733
  }
734
  list($name, $display_id) = $explode;
735
  // Load the view
736
  if ($view = views_get_view($name)) {
737
    if ($view->access($display_id)) {
738
      $output = $view->execute_display($display_id);
739
      // Before returning the block output, convert it to a renderable array
740
      // with contextual links.
741
      views_add_block_contextual_links($output, $view, $display_id);
742
      $view->destroy();
743
      return $output;
744
    }
745
    $view->destroy();
746
  }
747
}
748

    
749
/**
750
 * Converts Views block content to a renderable array with contextual links.
751
 *
752
 * @param $block
753
 *   An array representing the block, with the same structure as the return
754
 *   value of hook_block_view(). This will be modified so as to force
755
 *   $block['content'] to be a renderable array, containing the optional
756
 *   '#contextual_links' property (if there are any contextual links associated
757
 *   with the block).
758
 * @param $view
759
 *   The view that was used to generate the block content.
760
 * @param $display_id
761
 *   The ID of the display within the view that was used to generate the block
762
 *   content.
763
 * @param $block_type
764
 *   The type of the block. If it's block it's a regular views display,
765
 *   but 'special_block_-exp' exist as well.
766
 */
767
function views_add_block_contextual_links(&$block, $view, $display_id, $block_type = 'block') {
768
  // Do not add contextual links to an empty block.
769
  if (!empty($block['content'])) {
770
    // Contextual links only work on blocks whose content is a renderable
771
    // array, so if the block contains a string of already-rendered markup,
772
    // convert it to an array.
773
    if (is_string($block['content'])) {
774
      $block['content'] = array('#markup' => $block['content']);
775
    }
776
    // Add the contextual links.
777
    views_add_contextual_links($block['content'], $block_type, $view, $display_id);
778
  }
779
}
780

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

    
894
/**
895
 * Returns an array of language names.
896
 *
897
 * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
898
 *
899
 * @param $field
900
 *   'name' => names in current language, localized
901
 *   'native' => native names
902
 * @param $all
903
 *   Boolean to return all languages or only enabled ones
904
 *
905
 * @see locale_language_list()
906
 */
907
function views_language_list($field = 'name', $all = FALSE) {
908
  if ($all) {
909
    $languages = language_list();
910
  }
911
  else {
912
    $languages = language_list('enabled');
913
    $languages = $languages[1];
914
  }
915
  $list = array();
916
  foreach ($languages as $language) {
917
    $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
918
  }
919
  return $list;
920
}
921

    
922
/**
923
 * Implements hook_flush_caches().
924
 */
925
function views_flush_caches() {
926
  return array('cache_views', 'cache_views_data');
927
}
928

    
929
/**
930
 * Implements hook_field_create_instance.
931
 */
932
function views_field_create_instance($instance) {
933
  cache_clear_all('*', 'cache_views', TRUE);
934
  cache_clear_all('*', 'cache_views_data', TRUE);
935
}
936

    
937
/**
938
 * Implements hook_field_update_instance.
939
 */
940
function views_field_update_instance($instance, $prior_instance) {
941
  cache_clear_all('*', 'cache_views', TRUE);
942
  cache_clear_all('*', 'cache_views_data', TRUE);
943
}
944

    
945
/**
946
 * Implements hook_field_delete_instance.
947
 */
948
function views_field_delete_instance($instance) {
949
  cache_clear_all('*', 'cache_views', TRUE);
950
  cache_clear_all('*', 'cache_views_data', TRUE);
951
}
952

    
953
/**
954
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
955
 */
956
function views_invalidate_cache() {
957
  // Clear the views cache.
958
  cache_clear_all('*', 'cache_views', TRUE);
959

    
960
  // Clear the page and block cache.
961
  cache_clear_all();
962

    
963
  // Set the menu as needed to be rebuilt.
964
  variable_set('menu_rebuild_needed', TRUE);
965

    
966
  // Allow modules to respond to the Views cache being cleared.
967
  module_invoke_all('views_invalidate_cache');
968
}
969

    
970
/**
971
 * Access callback to determine if the user can import Views.
972
 *
973
 * View imports require an additional access check because they are PHP
974
 * code and PHP is more locked down than administer views.
975
 */
976
function views_import_access() {
977
  return user_access('administer views') && user_access('use PHP for settings');
978
}
979

    
980
/**
981
 * Determine if the logged in user has access to a view.
982
 *
983
 * This function should only be called from a menu hook or some other
984
 * embedded source. Each argument is the result of a call to
985
 * views_plugin_access::get_access_callback() which is then used
986
 * to determine if that display is accessible. If *any* argument
987
 * is accessible, then the view is accessible.
988
 */
989
function views_access() {
990
  $args = func_get_args();
991
  foreach ($args as $arg) {
992
    if ($arg === TRUE) {
993
      return TRUE;
994
    }
995

    
996
    if (!is_array($arg)) {
997
      continue;
998
    }
999

    
1000
    list($callback, $arguments) = $arg;
1001
    $arguments = $arguments ? $arguments : array();
1002
    // Bring dynamic arguments to the access callback.
1003
    foreach ($arguments as $key => $value) {
1004
      if (is_int($value) && isset($args[$value])) {
1005
        $arguments[$key] = $args[$value];
1006
      }
1007
    }
1008
    if (function_exists($callback) && call_user_func_array($callback, $arguments)) {
1009
      return TRUE;
1010
    }
1011
  }
1012

    
1013
  return FALSE;
1014
}
1015

    
1016
/**
1017
 * Access callback for the views_plugin_access_perm access plugin.
1018
 *
1019
 * Determine if the specified user has access to a view on the basis of
1020
 * permissions. If the $account argument is omitted, the current user
1021
 * is used.
1022
 */
1023
function views_check_perm($perms, $account = NULL) {
1024
  // Backward compatibility to ensure also a single permission string is
1025
  // properly processed.
1026
  $perms = is_array($perms) ? $perms : array($perms);
1027
  if (user_access('access all views', $account)) {
1028
    return TRUE;
1029
  }
1030
  // Perms are handled as OR, as soon one permission allows access TRUE is
1031
  // returned.
1032
  foreach ($perms as $perm) {
1033
    if (user_access($perm, $account)) {
1034
      return TRUE;
1035
    }
1036
  }
1037
  return FALSE;
1038
}
1039

    
1040
/**
1041
 * Access callback for the views_plugin_access_role access plugin.
1042

    
1043
 * Determine if the specified user has access to a view on the basis of any of
1044
 * the requested roles. If the $account argument is omitted, the current user
1045
 * is used.
1046
 */
1047
function views_check_roles($rids, $account = NULL) {
1048
  global $user;
1049
  $account = isset($account) ? $account : $user;
1050
  $roles = array_keys($account->roles);
1051
  $roles[] = $account->uid ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID;
1052
  return user_access('access all views', $account) || array_intersect(array_filter($rids), $roles);
1053
}
1054
// ------------------------------------------------------------------
1055
// Functions to help identify views that are running or ran
1056

    
1057
/**
1058
 * Set the current 'page view' that is being displayed so that it is easy
1059
 * for other modules or the theme to identify.
1060
 */
1061
function &views_set_page_view($view = NULL) {
1062
  static $cache = NULL;
1063
  if (isset($view)) {
1064
    $cache = $view;
1065
  }
1066

    
1067
  return $cache;
1068
}
1069

    
1070
/**
1071
 * Find out what, if any, page view is currently in use. Please note that
1072
 * this returns a reference, so be careful! You can unintentionally modify the
1073
 * $view object.
1074
 *
1075
 * @return view
1076
 *   A fully formed, empty $view object.
1077
 */
1078
function &views_get_page_view() {
1079
  return views_set_page_view();
1080
}
1081

    
1082
/**
1083
 * Set the current 'current view' that is being built/rendered so that it is
1084
 * easy for other modules or items in drupal_eval to identify
1085
 *
1086
 * @return view
1087
 */
1088
function &views_set_current_view($view = NULL) {
1089
  static $cache = NULL;
1090
  if (isset($view)) {
1091
    $cache = $view;
1092
  }
1093

    
1094
  return $cache;
1095
}
1096

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

    
1108
// ------------------------------------------------------------------
1109
// Include file helpers
1110

    
1111
/**
1112
 * Include views .inc files as necessary.
1113
 */
1114
function views_include($file) {
1115
  ctools_include($file, 'views');
1116
}
1117

    
1118
/**
1119
 * Load views files on behalf of modules.
1120
 */
1121
function views_module_include($api, $reset = FALSE) {
1122
  if ($reset) {
1123
    $cache = &drupal_static('ctools_plugin_api_info');
1124
    if (isset($cache['views']['views'])) {
1125
      unset($cache['views']['views']);
1126
    }
1127
  }
1128
  ctools_include('plugins');
1129
  return ctools_plugin_api_include('views', $api, views_api_minimum_version(), views_api_version());
1130
}
1131

    
1132
/**
1133
 * Get a list of modules that support the current views API.
1134
 */
1135
function views_get_module_apis($api = 'views', $reset = FALSE) {
1136
  if ($reset) {
1137
    $cache = &drupal_static('ctools_plugin_api_info');
1138
    if (isset($cache['views']['views'])) {
1139
      unset($cache['views']['views']);
1140
    }
1141
  }
1142
  ctools_include('plugins');
1143
  return ctools_plugin_api_info('views', $api, views_api_minimum_version(), views_api_version());
1144
}
1145

    
1146
/**
1147
 * Include views .css files.
1148
 */
1149
function views_add_css($file) {
1150
  // We set preprocess to FALSE because we are adding the files conditionally,
1151
  // and we don't want to generate duplicate cache files.
1152
  // TODO: at some point investigate adding some files unconditionally and
1153
  // allowing preprocess.
1154
  drupal_add_css(drupal_get_path('module', 'views') . "/css/$file.css", array('preprocess' => FALSE));
1155
}
1156

    
1157
/**
1158
 * Include views .js files.
1159
 */
1160
function views_add_js($file) {
1161
  // If javascript has been disabled by the user, never add js files.
1162
  if (variable_get('views_no_javascript', FALSE)) {
1163
    return;
1164
  }
1165
  static $base = TRUE, $ajax = TRUE;
1166
  if ($base) {
1167
    drupal_add_js(drupal_get_path('module', 'views') . "/js/base.js");
1168
    $base = FALSE;
1169
  }
1170
  if ($ajax && in_array($file, array('ajax', 'ajax_view'))) {
1171
    drupal_add_library('system', 'drupal.ajax');
1172
    drupal_add_library('system', 'jquery.form');
1173
    $ajax = FALSE;
1174
  }
1175
  ctools_add_js($file, 'views');
1176
}
1177

    
1178
/**
1179
 * Load views files on behalf of modules.
1180
 */
1181
function views_include_handlers($reset = FALSE) {
1182
  static $finished = FALSE;
1183
  // Ensure this only gets run once.
1184
  if ($finished && !$reset) {
1185
    return;
1186
  }
1187

    
1188
  views_include('base');
1189
  views_include('handlers');
1190
  views_include('cache');
1191
  views_include('plugins');
1192
  views_module_include('views', $reset);
1193
  $finished = TRUE;
1194
}
1195

    
1196
// -----------------------------------------------------------------------
1197
// Views handler functions
1198

    
1199
/**
1200
 * Fetch a handler from the data cache.
1201
 *
1202
 * @param $table
1203
 *   The name of the table this handler is from.
1204
 * @param $field
1205
 *   The name of the field this handler is from.
1206
 * @param $key
1207
 *   The type of handler. i.e, sort, field, argument, filter, relationship
1208
 * @param $override
1209
 *   Override the actual handler object with this class. Used for
1210
 *   aggregation when the handler is redirected to the aggregation
1211
 *   handler.
1212
 *
1213
 * @return views_handler
1214
 *   An instance of a handler object. May be views_handler_broken.
1215
 */
1216
function views_get_handler($table, $field, $key, $override = NULL) {
1217
  static $recursion_protection = array();
1218

    
1219
  $data = views_fetch_data($table, FALSE);
1220
  $handler = NULL;
1221
  views_include('handlers');
1222

    
1223
  // Support old views_data entries conversion.
1224

    
1225
  // Support conversion on table level.
1226
  if (isset($data['moved to'])) {
1227
    $moved = array($data['moved to'], $field);
1228
  }
1229
  // Support conversion on datafield level.
1230
  if (isset($data[$field]['moved to'])) {
1231
    $moved = $data[$field]['moved to'];
1232
  }
1233
  // Support conversion on handler level.
1234
  if (isset($data[$field][$key]['moved to'])) {
1235
    $moved = $data[$field][$key]['moved to'];
1236
  }
1237

    
1238
  if (isset($data[$field][$key]) || !empty($moved)) {
1239
    if (!empty($moved)) {
1240
      list($moved_table, $moved_field) = $moved;
1241
      if (!empty($recursion_protection[$moved_table][$moved_field])) {
1242
        // recursion detected!
1243
        return NULL;
1244
      }
1245

    
1246
      $recursion_protection[$moved_table][$moved_field] = TRUE;
1247
      $handler = views_get_handler($moved_table, $moved_field, $key, $override);
1248
      $recursion_protection = array();
1249
      if ($handler) {
1250
        // store these values so we know what we were originally called.
1251
        $handler->original_table = $table;
1252
        $handler->original_field = $field;
1253
        if (empty($handler->actual_table)) {
1254
          $handler->actual_table = $moved_table;
1255
          $handler->actual_field = $moved_field;
1256
        }
1257
      }
1258
      return $handler;
1259
    }
1260

    
1261
    // Set up a default handler:
1262
    if (empty($data[$field][$key]['handler'])) {
1263
      $data[$field][$key]['handler'] = 'views_handler_' . $key;
1264
    }
1265

    
1266
    if ($override) {
1267
      $data[$field][$key]['override handler'] = $override;
1268
    }
1269

    
1270
    $handler = _views_prepare_handler($data[$field][$key], $data, $field, $key);
1271
  }
1272

    
1273
  if ($handler) {
1274
    return $handler;
1275
  }
1276

    
1277
  // DEBUG -- identify missing handlers
1278
  vpr("Missing handler: @table @field @key", array('@table' => $table, '@field' => $field, '@key' => $key));
1279
  $broken = array(
1280
    'title' => t('Broken handler @table.@field', array('@table' => $table, '@field' => $field)),
1281
    'handler' => 'views_handler_' . $key . '_broken',
1282
    'table' => $table,
1283
    'field' => $field,
1284
  );
1285
  return _views_create_handler($broken, 'handler', $key);
1286
}
1287

    
1288
/**
1289
 * Fetch Views' data from the cache
1290
 */
1291
function views_fetch_data($table = NULL, $move = TRUE, $reset = FALSE) {
1292
  views_include('cache');
1293
  return _views_fetch_data($table, $move, $reset);
1294
}
1295

    
1296
// -----------------------------------------------------------------------
1297
// Views plugin functions
1298

    
1299
/**
1300
 * Fetch the plugin data from cache.
1301
 */
1302
function views_fetch_plugin_data($type = NULL, $plugin = NULL, $reset = FALSE) {
1303
  views_include('cache');
1304
  return _views_fetch_plugin_data($type, $plugin, $reset);
1305
}
1306

    
1307
/**
1308
 * Fetch a list of all base tables available
1309
 *
1310
 * @param $type
1311
 *   Either 'display', 'style' or 'row'
1312
 * @param $key
1313
 *   For style plugins, this is an optional type to restrict to. May be 'normal',
1314
 *   'summary', 'feed' or others based on the needs of the display.
1315
 * @param $base
1316
 *   An array of possible base tables.
1317
 *
1318
 * @return
1319
 *   A keyed array of in the form of 'base_table' => 'Description'.
1320
 */
1321
function views_fetch_plugin_names($type, $key = NULL, $base = array()) {
1322
  $data = views_fetch_plugin_data();
1323

    
1324
  $plugins[$type] = array();
1325

    
1326
  foreach ($data[$type] as $id => $plugin) {
1327
    // Skip plugins that don't conform to our key.
1328
    if ($key && (empty($plugin['type']) || $plugin['type'] != $key)) {
1329
      continue;
1330
    }
1331
    if (empty($plugin['no ui']) && (empty($base) || empty($plugin['base']) || array_intersect($base, $plugin['base']))) {
1332
      $plugins[$type][$id] = $plugin['title'];
1333
    }
1334
  }
1335

    
1336
  if (!empty($plugins[$type])) {
1337
    asort($plugins[$type]);
1338
    return $plugins[$type];
1339
  }
1340
  // fall-through
1341
  return array();
1342
}
1343

    
1344
/**
1345
 * Get a handler for a plugin
1346
 *
1347
 * @return views_plugin
1348
 *
1349
 * The created plugin object.
1350
 */
1351
function views_get_plugin($type, $plugin, $reset = FALSE) {
1352
  views_include('handlers');
1353
  $definition = views_fetch_plugin_data($type, $plugin, $reset);
1354
  if (!empty($definition)) {
1355
    return _views_create_handler($definition, $type);
1356
  }
1357
}
1358

    
1359
/**
1360
 * Load the current enabled localization plugin.
1361
 *
1362
 * @return The name of the localization plugin.
1363
 */
1364
function views_get_localization_plugin() {
1365
  $plugin = variable_get('views_localization_plugin', '');
1366
  // Provide sane default values for the localization plugin.
1367
  if (empty($plugin)) {
1368
    if (module_exists('locale')) {
1369
      $plugin = 'core';
1370
    }
1371
    else {
1372
      $plugin = 'none';
1373
    }
1374
  }
1375

    
1376
  return $plugin;
1377
}
1378

    
1379
// -----------------------------------------------------------------------
1380
// Views database functions
1381

    
1382
/**
1383
 * Get all view templates.
1384
 *
1385
 * Templates are special in-code views that are never active, but exist only
1386
 * to be cloned into real views as though they were templates.
1387
 */
1388
function views_get_all_templates() {
1389
  $templates = array();
1390
  $modules = views_module_include('views_template');
1391

    
1392
  foreach ($modules as $module => $info) {
1393
    $function = $module . '_views_templates';
1394
    if (function_exists($function)) {
1395
      $new = $function();
1396
      if ($new && is_array($new)) {
1397
        $templates = array_merge($new, $templates);
1398
      }
1399
    }
1400
  }
1401

    
1402
  return $templates;
1403
}
1404

    
1405
/**
1406
 * Create an empty view to work with.
1407
 *
1408
 * @return view
1409
 *   A fully formed, empty $view object. This object must be populated before
1410
 *   it can be successfully saved.
1411
 */
1412
function views_new_view() {
1413
  views_include('view');
1414
  $view = new view();
1415
  $view->vid = 'new';
1416
  $view->add_display('default');
1417

    
1418
  return $view;
1419
}
1420

    
1421
/**
1422
 * Return a list of all views and display IDs that have a particular
1423
 * setting in their display's plugin settings.
1424
 *
1425
 * @return
1426
 * @code
1427
 * array(
1428
 *   array($view, $display_id),
1429
 *   array($view, $display_id),
1430
 * );
1431
 * @endcode
1432
 */
1433
function views_get_applicable_views($type) {
1434
  // @todo: Use a smarter flagging system so that we don't have to
1435
  // load every view for this.
1436
  $result = array();
1437
  $views = views_get_all_views();
1438

    
1439
  foreach ($views as $view) {
1440
    // Skip disabled views.
1441
    if (!empty($view->disabled)) {
1442
      continue;
1443
    }
1444

    
1445
    if (empty($view->display)) {
1446
      // Skip this view as it is broken.
1447
      vsm(t("Skipping broken view @view", array('@view' => $view->name)));
1448
      continue;
1449
    }
1450

    
1451
    // Loop on array keys because something seems to muck with $view->display
1452
    // a bit in PHP4.
1453
    foreach (array_keys($view->display) as $id) {
1454
      $plugin = views_fetch_plugin_data('display', $view->display[$id]->display_plugin);
1455
      if (!empty($plugin[$type])) {
1456
        // This view uses hook menu. Clone it so that different handlers
1457
        // don't trip over each other, and add it to the list.
1458
        $v = $view->clone_view();
1459
        if ($v->set_display($id) && $v->display_handler->get_option('enabled')) {
1460
          $result[] = array($v, $id);
1461
        }
1462
        // In PHP 4.4.7 and presumably earlier, if we do not unset $v
1463
        // here, we will find that it actually overwrites references
1464
        // possibly due to shallow copying issues.
1465
        unset($v);
1466
      }
1467
    }
1468
  }
1469
  return $result;
1470
}
1471

    
1472
/**
1473
 * Return an array of all views as fully loaded $view objects.
1474
 *
1475
 * @param $reset
1476
 *   If TRUE, reset the static cache forcing views to be reloaded.
1477
 */
1478
function views_get_all_views($reset = FALSE) {
1479
  ctools_include('export');
1480
  return ctools_export_crud_load_all('views_view', $reset);
1481
}
1482

    
1483
/**
1484
 * Returns an array of all enabled views, as fully loaded $view objects.
1485
 */
1486
function views_get_enabled_views() {
1487
  $views = views_get_all_views();
1488
  return array_filter($views, 'views_view_is_enabled');
1489
}
1490

    
1491
/**
1492
 * Returns an array of all disabled views, as fully loaded $view objects.
1493
 */
1494
function views_get_disabled_views() {
1495
  $views = views_get_all_views();
1496
  return array_filter($views, 'views_view_is_disabled');
1497
}
1498

    
1499
/**
1500
 * Return an array of view as options array, that can be used by select,
1501
 * checkboxes and radios as #options.
1502
 *
1503
 * @param bool $views_only
1504
 *  If TRUE, only return views, not displays.
1505
 * @param string $filter
1506
 *  Filters the views on status. Can either be 'all' (default), 'enabled' or
1507
 *  'disabled'
1508
 * @param  mixed $exclude_view
1509
 *  view or current display to exclude
1510
 *  either a
1511
 *  - views object (containing $exclude_view->name and $exclude_view->current_display)
1512
 *  - views name as string:  e.g. my_view
1513
 *  - views name and display id (separated by ':'): e.g. my_view:default
1514
 * @param bool $optgroup
1515
 *  If TRUE, returns an array with optgroups for each view (will be ignored for
1516
 *  $views_only = TRUE). Can be used by select
1517
 * @param bool $sort
1518
 *  If TRUE, the list of views is sorted ascending.
1519
 *
1520
 * @return array
1521
 *  an associative array for use in select.
1522
 *  - key: view name and display id separated by ':', or the view name only
1523
 */
1524
function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE, $sort = FALSE) {
1525

    
1526
  // Filter the big views array.
1527
  switch ($filter) {
1528
    case 'all':
1529
    case 'disabled':
1530
    case 'enabled':
1531
      $func = "views_get_{$filter}_views";
1532
      $views = $func();
1533
      break;
1534
    default:
1535
      return array();
1536
  }
1537

    
1538
  // Prepare exclude view strings for comparison.
1539
  if (empty($exclude_view)) {
1540
    $exclude_view_name = '';
1541
    $exclude_view_display = '';
1542
  }
1543
  elseif (is_object($exclude_view)) {
1544
    $exclude_view_name = $exclude_view->name;
1545
    $exclude_view_display = $exclude_view->current_display;
1546
  }
1547
  else {
1548
    list($exclude_view_name, $exclude_view_display) = explode(':', $exclude_view);
1549
  }
1550

    
1551
  $options = array();
1552
  foreach ($views as $view) {
1553
    // Return only views.
1554
    if ($views_only && $view->name != $exclude_view_name) {
1555
      $options[$view->name] = $view->get_human_name();
1556
    }
1557
    // Return views with display ids.
1558
    else {
1559
      foreach ($view->display as $display_id => $display) {
1560
        if (!($view->name == $exclude_view_name && $display_id == $exclude_view_display)) {
1561
          if ($optgroup) {
1562
            $options[$view->name][$view->name . ':' . $display->id] = t('@view : @display', array('@view' => $view->name, '@display' => $display->id));
1563
          }
1564
          else {
1565
            $options[$view->name . ':' . $display->id] = t('View: @view - Display: @display', array('@view' => $view->name, '@display' => $display->id));
1566
          }
1567
        }
1568
      }
1569
    }
1570
  }
1571
  if ($sort) {
1572
    ksort($options);
1573
  }
1574
  return $options;
1575
}
1576

    
1577
/**
1578
 * Returns TRUE if a view is enabled, FALSE otherwise.
1579
 */
1580
function views_view_is_enabled($view) {
1581
  return empty($view->disabled);
1582
}
1583

    
1584
/**
1585
 * Returns TRUE if a view is disabled, FALSE otherwise.
1586
 */
1587
function views_view_is_disabled($view) {
1588
  return !empty($view->disabled);
1589
}
1590

    
1591
/**
1592
 * Get a view from the database or from default views.
1593
 *
1594
 * This function is just a static wrapper around views::load(). This function
1595
 * isn't called 'views_load()' primarily because it might get a view
1596
 * from the default views which aren't technically loaded from the database.
1597
 *
1598
 * @param $name
1599
 *   The name of the view.
1600
 * @param $reset
1601
 *   If TRUE, reset this entry in the load cache.
1602
 * @return view
1603
 *   A reference to the $view object. Use $reset if you're sure you want
1604
 *   a fresh one.
1605
 */
1606
function views_get_view($name, $reset = FALSE) {
1607
  if ($reset) {
1608
    $cache = &drupal_static('ctools_export_load_object');
1609
    if (isset($cache['views_view'][$name])) {
1610
      unset($cache['views_view'][$name]);
1611
    }
1612
  }
1613

    
1614
  ctools_include('export');
1615
  $view = ctools_export_crud_load('views_view', $name);
1616
  if ($view) {
1617
    $view->update();
1618
    return $view->clone_view();
1619
  }
1620
}
1621

    
1622
/**
1623
 * Find the real location of a table.
1624
 *
1625
 * If a table has moved, find the new name of the table so that we can
1626
 * change its name directly in options where necessary.
1627
 */
1628
function views_move_table($table) {
1629
  $data = views_fetch_data($table, FALSE);
1630
  if (isset($data['moved to'])) {
1631
    $table = $data['moved to'];
1632
  }
1633

    
1634
  return $table;
1635
}
1636

    
1637
/**
1638
 * Export callback to load the view subrecords, which are the displays.
1639
 */
1640
function views_load_display_records(&$views) {
1641
  // Get vids from the views.
1642
  $names = array();
1643
  foreach ($views as $view) {
1644
    if (empty($view->display)) {
1645
      $names[$view->vid] = $view->name;
1646
    }
1647
  }
1648

    
1649
  if (empty($names)) {
1650
    return;
1651
  }
1652

    
1653
  foreach (view::db_objects() as $key) {
1654
    $object_name = "views_$key";
1655
    $result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
1656
      array(':vids' => array_keys($names)));
1657

    
1658
    foreach ($result as $data) {
1659
      $object = new $object_name(FALSE);
1660
      $object->load_row($data);
1661

    
1662
      // Because it can get complicated with this much indirection,
1663
      // make a shortcut reference.
1664
      $location = &$views[$names[$object->vid]]->$key;
1665

    
1666
      // If we have a basic id field, load the item onto the view based on
1667
      // this ID, otherwise push it on.
1668
      if (!empty($object->id)) {
1669
        $location[$object->id] = $object;
1670
      }
1671
      else {
1672
        $location[] = $object;
1673
      }
1674
    }
1675
  }
1676
}
1677

    
1678
/**
1679
 * Export CRUD callback to save a view.
1680
 */
1681
function views_save_view(&$view) {
1682
  return $view->save();
1683
}
1684

    
1685
/**
1686
 * Export CRUD callback to delete a view.
1687
 */
1688
function views_delete_view(&$view) {
1689
  return $view->delete(TRUE);
1690
}
1691

    
1692
/**
1693
 * Export CRUD callback to export a view.
1694
 */
1695
function views_export_view(&$view, $indent = '') {
1696
  return $view->export($indent);
1697
}
1698

    
1699
/**
1700
 * Export callback to change view status.
1701
 */
1702
function views_export_status($view, $status) {
1703
  ctools_export_set_object_status($view, $status);
1704
  views_invalidate_cache();
1705
}
1706

    
1707
// ------------------------------------------------------------------
1708
// Views debug helper functions
1709

    
1710
/**
1711
 * Provide debug output for Views.
1712
 *
1713
 * This relies on devel.module
1714
 * or on the debug() function if you use a simpletest.
1715
 *
1716
 * @param $message
1717
 *   The message/variable which should be debugged.
1718
 *   This either could be
1719
 *     * an array/object which is converted to pretty output
1720
 *     * a translation source string which is used together with the parameter placeholders.
1721
 *
1722
 * @param $placeholder
1723
 *   The placeholders which are used for the translation source string.
1724
 */
1725
function views_debug($message, $placeholders = array()) {
1726
  if (!is_string($message)) {
1727
    $output = '<pre>' . var_export($message, TRUE) . '</pre>';
1728
  }
1729
  if (module_exists('devel') && variable_get('views_devel_output', FALSE) && user_access('access devel information')) {
1730
    $devel_region = variable_get('views_devel_region', 'footer');
1731
    if ($devel_region == 'watchdog') {
1732
      $output = $message;
1733
      watchdog('views_logging', $output, $placeholders);
1734
    }
1735
    else if ($devel_region == 'drupal_debug') {
1736
      $output = empty($output) ? t($message, $placeholders) : $output;
1737
      dd($output);
1738
    }
1739
    else {
1740
      $output = empty($output) ? t($message, $placeholders) : $output;
1741
      dpm($output);
1742
    }
1743
  }
1744
  elseif (isset($GLOBALS['drupal_test_info'])) {
1745
    $output = empty($output) ? t($message, $placeholders) : $output;
1746
    debug($output);
1747
  }
1748
}
1749

    
1750
/**
1751
 * Shortcut to views_debug()
1752
 */
1753
function vpr($message, $placeholders = array()) {
1754
  views_debug($message, $placeholders);
1755
}
1756

    
1757
/**
1758
 * Debug messages
1759
 */
1760
function vsm($message) {
1761
  if (module_exists('devel')) {
1762
    dpm($message);
1763
  }
1764
}
1765

    
1766
function views_trace() {
1767
  $message = '';
1768
  foreach (debug_backtrace() as $item) {
1769
    if (!empty($item['file']) && !in_array($item['function'], array('vsm_trace', 'vpr_trace', 'views_trace'))) {
1770
      $message .= basename($item['file']) . ": " . (empty($item['class']) ? '' : ($item['class'] . '->')) . "$item[function] line $item[line]" . "\n";
1771
    }
1772
  }
1773
  return $message;
1774
}
1775

    
1776
function vsm_trace() {
1777
  vsm(views_trace());
1778
}
1779

    
1780
function vpr_trace() {
1781
  dpr(views_trace());
1782
}
1783

    
1784
// ------------------------------------------------------------------
1785
// Views form (View with form elements)
1786

    
1787
/**
1788
 * Returns TRUE if the passed-in view contains handlers with views form
1789
 * implementations, FALSE otherwise.
1790
 */
1791
function views_view_has_form_elements($view) {
1792
  foreach ($view->field as $field) {
1793
    if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
1794
      return TRUE;
1795
    }
1796
  }
1797
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1798
  $empty = empty($view->result);
1799
  foreach ($area_handlers as $area) {
1800
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1801
      return TRUE;
1802
    }
1803
  }
1804
  return FALSE;
1805
}
1806

    
1807
/**
1808
 * This is the entry function. Just gets the form for the current step.
1809
 * The form is always assumed to be multistep, even if it has only one
1810
 * step (the default 'views_form_views_form' step). That way it is actually
1811
 * possible for modules to have a multistep form if they need to.
1812
 */
1813
function views_form($form, &$form_state, $view, $output) {
1814
  $form_state['step'] = isset($form_state['step']) ? $form_state['step'] : 'views_form_views_form';
1815
  // Cache the built form to prevent it from being rebuilt prior to validation
1816
  // and submission, which could lead to data being processed incorrectly,
1817
  // because the views rows (and thus, the form elements as well) have changed
1818
  // in the meantime.
1819
  $form_state['cache'] = TRUE;
1820

    
1821
  $form = array();
1822
  $query = drupal_get_query_parameters($_GET, array('q'));
1823
  $form['#action'] = url($view->get_url(), array('query' => $query));
1824
  // Tell the preprocessor whether it should hide the header, footer, pager...
1825
  $form['show_view_elements'] = array(
1826
    '#type' => 'value',
1827
    '#value' => ($form_state['step'] == 'views_form_views_form') ? TRUE : FALSE,
1828
  );
1829

    
1830
  $form = $form_state['step']($form, $form_state, $view, $output);
1831
  return $form;
1832
}
1833

    
1834
/**
1835
 * Callback for the main step of a Views form.
1836
 * Invoked by views_form().
1837
 */
1838
function views_form_views_form($form, &$form_state, $view, $output) {
1839
  $form['#prefix'] = '<div class="views-form">';
1840
  $form['#suffix'] = '</div>';
1841
  $form['#theme'] = 'views_form_views_form';
1842
  $form['#validate'][] = 'views_form_views_form_validate';
1843
  $form['#submit'][] = 'views_form_views_form_submit';
1844

    
1845
  // Add the output markup to the form array so that it's included when the form
1846
  // array is passed to the theme function.
1847
  $form['output'] = array(
1848
    '#type' => 'markup',
1849
    '#markup' => $output,
1850
    // This way any additional form elements will go before the view
1851
    // (below the exposed widgets).
1852
    '#weight' => 50,
1853
  );
1854

    
1855
  $substitutions = array();
1856
  foreach ($view->field as $field_name => $field) {
1857
    $form_element_name = $field_name;
1858
    if (method_exists($field, 'form_element_name')) {
1859
      $form_element_name = $field->form_element_name();
1860
    }
1861
    $method_form_element_row_id_exists = FALSE;
1862
    if (method_exists($field, 'form_element_row_id')) {
1863
      $method_form_element_row_id_exists = TRUE;
1864
    }
1865

    
1866
    // If the field provides a views form, allow it to modify the $form array.
1867
    $has_form = FALSE;
1868
    if (property_exists($field, 'views_form_callback')) {
1869
      $callback = $field->views_form_callback;
1870
      $callback($view, $field, $form, $form_state);
1871
      $has_form = TRUE;
1872
    }
1873
    elseif (method_exists($field, 'views_form')) {
1874
      $field->views_form($form, $form_state);
1875
      $has_form = TRUE;
1876
    }
1877

    
1878
    // Build the substitutions array for use in the theme function.
1879
    if ($has_form) {
1880
      foreach ($view->result as $row_id => $row) {
1881
        if ($method_form_element_row_id_exists) {
1882
          $form_element_row_id = $field->form_element_row_id($row_id);
1883
        }
1884
        else {
1885
          $form_element_row_id = $row_id;
1886
        }
1887

    
1888
        $substitutions[] = array(
1889
          'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
1890
          'field_name' => $form_element_name,
1891
          'row_id' => $form_element_row_id,
1892
        );
1893
      }
1894
    }
1895
  }
1896

    
1897
  // Give the area handlers a chance to extend the form.
1898
  $area_handlers = array_merge(array_values($view->header), array_values($view->footer));
1899
  $empty = empty($view->result);
1900
  foreach ($area_handlers as $area) {
1901
    if (method_exists($area, 'views_form') && !$area->views_form_empty($empty)) {
1902
      $area->views_form($form, $form_state);
1903
    }
1904
  }
1905

    
1906
  $form['#substitutions'] = array(
1907
    '#type' => 'value',
1908
    '#value' => $substitutions,
1909
  );
1910
  $form['actions'] = array(
1911
    '#type' => 'container',
1912
    '#attributes' => array('class' => array('form-actions')),
1913
    '#weight' => 100,
1914
  );
1915
  $form['actions']['submit'] = array(
1916
    '#type' => 'submit',
1917
    '#value' => t('Save'),
1918
  );
1919

    
1920
  return $form;
1921
}
1922

    
1923
/**
1924
 * Validate handler for the first step of the views form.
1925
 * Calls any existing views_form_validate functions located
1926
 * on the views fields.
1927
 */
1928
function views_form_views_form_validate($form, &$form_state) {
1929
  $view = $form_state['build_info']['args'][0];
1930

    
1931
  // Call the validation method on every field handler that has it.
1932
  foreach ($view->field as $field_name => $field) {
1933
    if (method_exists($field, 'views_form_validate')) {
1934
      $field->views_form_validate($form, $form_state);
1935
    }
1936
  }
1937

    
1938
  // Call the validate method on every area handler that has it.
1939
  foreach (array('header', 'footer') as $area) {
1940
    foreach ($view->{$area} as $area_name => $area_handler) {
1941
      if (method_exists($area_handler, 'views_form_validate')) {
1942
        $area_handler->views_form_validate($form, $form_state);
1943
      }
1944
    }
1945
  }
1946
}
1947

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

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

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

    
1973
// ------------------------------------------------------------------
1974
// Exposed widgets form
1975

    
1976
/**
1977
 * Form builder for the exposed widgets form.
1978
 *
1979
 * Be sure that $view and $display are references.
1980
 */
1981
function views_exposed_form($form, &$form_state) {
1982
  // Don't show the form when batch operations are in progress.
1983
  if ($batch = batch_get() && isset($batch['current_set'])) {
1984
    return array(
1985
      // Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
1986
      '#theme' => '',
1987
    );
1988
  }
1989

    
1990
  // Make sure that we validate because this form might be submitted
1991
  // multiple times per page.
1992
  $form_state['must_validate'] = TRUE;
1993
  $view = &$form_state['view'];
1994
  $display = &$form_state['display'];
1995

    
1996
  $form_state['input'] = $view->get_exposed_input();
1997

    
1998
  // Let form plugins know this is for exposed widgets.
1999
  $form_state['exposed'] = TRUE;
2000
  // Check if the form was already created
2001
  if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
2002
    return $cache;
2003
  }
2004

    
2005
  $form['#info'] = array();
2006

    
2007
  if (!variable_get('clean_url', FALSE)) {
2008
    $form['q'] = array(
2009
      '#type' => 'hidden',
2010
      '#value' => $view->get_url(),
2011
    );
2012
  }
2013

    
2014
  // Go through each handler and let it generate its exposed widget.
2015
  foreach ($view->display_handler->handlers as $type => $value) {
2016
    foreach ($view->$type as $id => $handler) {
2017
      if ($handler->can_expose() && $handler->is_exposed()) {
2018
        // Grouped exposed filters have their own forms.
2019
        // Instead of render the standard exposed form, a new Select or
2020
        // Radio form field is rendered with the available groups.
2021
        // When an user choose an option the selected value is split
2022
        // into the operator and value that the item represents.
2023
        if ($handler->is_a_group()) {
2024
          $handler->group_form($form, $form_state);
2025
          $id = $handler->options['group_info']['identifier'];
2026
        }
2027
        else {
2028
          $handler->exposed_form($form, $form_state);
2029
        }
2030
        if ($info = $handler->exposed_info()) {
2031
          $form['#info']["$type-$id"] = $info;
2032
        }
2033
      }
2034
    }
2035
  }
2036

    
2037
  $form['submit'] = array(
2038
    '#name' => '', // prevent from showing up in $_GET.
2039
    '#type' => 'submit',
2040
    '#value' => t('Apply'),
2041
    '#id' => drupal_html_id('edit-submit-' . $view->name),
2042
  );
2043

    
2044
  $form['#action'] = url($view->display_handler->get_url());
2045
  $form['#theme'] = views_theme_functions('views_exposed_form', $view, $display);
2046
  $form['#id'] = drupal_clean_css_identifier('views_exposed_form-' . check_plain($view->name) . '-' . check_plain($display->id));
2047
//  $form['#attributes']['class'] = array('views-exposed-form');
2048

    
2049
  // If using AJAX, we need the form plugin.
2050
  if ($view->use_ajax) {
2051
    drupal_add_library('system', 'jquery.form');
2052
  }
2053
  ctools_include('dependent');
2054

    
2055
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2056
  $exposed_form_plugin->exposed_form_alter($form, $form_state);
2057

    
2058
  // Save the form
2059
  views_exposed_form_cache($view->name, $view->current_display, $form);
2060

    
2061
  return $form;
2062
}
2063

    
2064
/**
2065
 * Implement hook_form_alter for the exposed form.
2066
 *
2067
 * Since the exposed form is a GET form, we don't want it to send a wide
2068
 * variety of information.
2069
 */
2070
function views_form_views_exposed_form_alter(&$form, &$form_state) {
2071
  $form['form_build_id']['#access'] = FALSE;
2072
  $form['form_token']['#access'] = FALSE;
2073
  $form['form_id']['#access'] = FALSE;
2074
}
2075

    
2076
/**
2077
 * Validate handler for exposed filters
2078
 */
2079
function views_exposed_form_validate(&$form, &$form_state) {
2080
  foreach (array('field', 'filter') as $type) {
2081
    $handlers = &$form_state['view']->$type;
2082
    foreach ($handlers as $key => $handler) {
2083
      $handlers[$key]->exposed_validate($form, $form_state);
2084
    }
2085
  }
2086
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2087
  $exposed_form_plugin->exposed_form_validate($form, $form_state);
2088
}
2089

    
2090
/**
2091
 * Submit handler for exposed filters
2092
 */
2093
function views_exposed_form_submit(&$form, &$form_state) {
2094
  foreach (array('field', 'filter') as $type) {
2095
    $handlers = &$form_state['view']->$type;
2096
    foreach ($handlers as $key => $info) {
2097
      $handlers[$key]->exposed_submit($form, $form_state);
2098
    }
2099
  }
2100
  $form_state['view']->exposed_data = $form_state['values'];
2101
  $form_state['view']->exposed_raw_input = array();
2102

    
2103

    
2104
  $exclude = array('q', 'submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', '', 'reset');
2105
  $exposed_form_plugin = $form_state['exposed_form_plugin'];
2106
  $exposed_form_plugin->exposed_form_submit($form, $form_state, $exclude);
2107

    
2108
  foreach ($form_state['values'] as $key => $value) {
2109
    if (!in_array($key, $exclude)) {
2110
      $form_state['view']->exposed_raw_input[$key] = $value;
2111
    }
2112
  }
2113
}
2114

    
2115
/**
2116
 * Save the Views exposed form for later use.
2117
 *
2118
 * @param $views_name
2119
 *   String. The views name.
2120
 * @param $display_name
2121
 *   String. The current view display name.
2122
 * @param $form_output
2123
 *   Array (optional). The form structure. Only needed when inserting the value.
2124
 * @return
2125
 *   Array. The form structure, if any. Otherwise, return FALSE.
2126
 */
2127
function views_exposed_form_cache($views_name, $display_name, $form_output = NULL) {
2128
  // When running tests for exposed filters, this cache should
2129
  // be cleared between each test.
2130
  $views_exposed = &drupal_static(__FUNCTION__);
2131

    
2132
  // Save the form output
2133
  if (!empty($form_output)) {
2134
    $views_exposed[$views_name][$display_name] = $form_output;
2135
    return;
2136
  }
2137

    
2138
  // Return the form output, if any
2139
  return empty($views_exposed[$views_name][$display_name]) ? FALSE : $views_exposed[$views_name][$display_name];
2140
}
2141

    
2142
// ------------------------------------------------------------------
2143
// Misc helpers
2144

    
2145
/**
2146
 * Build a list of theme function names for use most everywhere.
2147
 */
2148
function views_theme_functions($hook, $view, $display = NULL) {
2149
  require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'views') . "/theme/theme.inc";
2150
  return _views_theme_functions($hook, $view, $display);
2151
}
2152

    
2153
/**
2154
 * Substitute current time; this works with cached queries.
2155
 */
2156
function views_views_query_substitutions($view) {
2157
  global $language_content;
2158
  return array(
2159
    '***CURRENT_VERSION***' => VERSION,
2160
    '***CURRENT_TIME***' => REQUEST_TIME,
2161
    '***CURRENT_LANGUAGE***' => $language_content->language,
2162
    '***DEFAULT_LANGUAGE***' => language_default('language'),
2163
  );
2164
}
2165

    
2166
/**
2167
 * Implements hook_query_TAG_alter().
2168
 *
2169
 * This is the hook_query_alter() for queries tagged by Views and is used to
2170
 * add in substitutions from hook_views_query_substitutions().
2171
 */
2172
function views_query_views_alter(QueryAlterableInterface $query) {
2173
  $substitutions = $query->getMetaData('views_substitutions');
2174
  $tables =& $query->getTables();
2175
  $where =& $query->conditions();
2176

    
2177
  // Replaces substitions in tables.
2178
  foreach ($tables as $table_name => $table_metadata) {
2179
    foreach ($table_metadata['arguments'] as $replacement_key => $value) {
2180
      if (isset($substitutions[$value])) {
2181
        $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
2182
      }
2183
    }
2184
  }
2185

    
2186
  // Replaces substitions in filter criterias.
2187
  _views_query_tag_alter_condition($query, $where, $substitutions);
2188
}
2189

    
2190
/**
2191
 * Replaces the substitutions recursive foreach condition.
2192
 */
2193
function _views_query_tag_alter_condition(QueryAlterableInterface $query, &$conditions, $substitutions) {
2194
  foreach ($conditions as $condition_id => &$condition) {
2195
    if (is_numeric($condition_id)) {
2196
      if (is_string($condition['field'])) {
2197
        $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
2198
      }
2199
      elseif (is_object($condition['field'])) {
2200
        $sub_conditions =& $condition['field']->conditions();
2201
        _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
2202
      }
2203
      // $condition['value'] is a subquery so alter the subquery recursive.
2204
      // Therefore take sure to get the metadata of the main query.
2205
      if (is_object($condition['value'])) {
2206
        $subquery = $condition['value'];
2207
        $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
2208
        views_query_views_alter($condition['value']);
2209
      }
2210
      elseif (isset($condition['value'])) {
2211
        $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
2212
      }
2213
    }
2214
  }
2215
}
2216

    
2217
/**
2218
 * Embed a view using a PHP snippet.
2219
 *
2220
 * This function is meant to be called from PHP snippets, should one wish to
2221
 * embed a view in a node or something. It's meant to provide the simplest
2222
 * solution and doesn't really offer a lot of options, but breaking the function
2223
 * apart is pretty easy, and this provides a worthwhile guide to doing so.
2224
 *
2225
 * Note that this function does NOT display the title of the view. If you want
2226
 * to do that, you will need to do what this function does manually, by
2227
 * loading the view, getting the preview and then getting $view->get_title().
2228
 *
2229
 * @param $name
2230
 *   The name of the view to embed.
2231
 * @param $display_id
2232
 *   The display id to embed. If unsure, use 'default', as it will always be
2233
 *   valid. But things like 'page' or 'block' should work here.
2234
 * @param ...
2235
 *   Any additional parameters will be passed as arguments.
2236
 */
2237
function views_embed_view($name, $display_id = 'default') {
2238
  $args = func_get_args();
2239
  array_shift($args); // remove $name
2240
  if (count($args)) {
2241
    array_shift($args); // remove $display_id
2242
  }
2243

    
2244
  $view = views_get_view($name);
2245
  if (!$view || !$view->access($display_id)) {
2246
    return;
2247
  }
2248

    
2249
  return $view->preview($display_id, $args);
2250
}
2251

    
2252
/**
2253
 * Get the result of a view.
2254
 *
2255
 * @param string $name
2256
 *   The name of the view to retrieve the data from.
2257
 * @param string $display_id
2258
 *   The display id. On the edit page for the view in question, you'll find
2259
 *   a list of displays at the left side of the control area. "Master"
2260
 *   will be at the top of that list. Hover your cursor over the name of the
2261
 *   display you want to use. An URL will appear in the status bar of your
2262
 *   browser. This is usually at the bottom of the window, in the chrome.
2263
 *   Everything after #views-tab- is the display ID, e.g. page_1.
2264
 * @param ...
2265
 *   Any additional parameters will be passed as arguments.
2266
 * @return array
2267
 *   An array containing an object for each view item.
2268
 */
2269
function views_get_view_result($name, $display_id = NULL) {
2270
  $args = func_get_args();
2271
  array_shift($args); // remove $name
2272
  if (count($args)) {
2273
    array_shift($args); // remove $display_id
2274
  }
2275

    
2276
  $view = views_get_view($name);
2277
  if (is_object($view)) {
2278
    if (is_array($args)) {
2279
      $view->set_arguments($args);
2280
    }
2281
    if (is_string($display_id)) {
2282
      $view->set_display($display_id);
2283
    }
2284
    else {
2285
      $view->init_display();
2286
    }
2287
    $view->pre_execute();
2288
    $view->execute();
2289
    return $view->result;
2290
  }
2291
  else {
2292
    return array();
2293
  }
2294
}
2295

    
2296
/**
2297
 * Export a field.
2298
 */
2299
function views_var_export($var, $prefix = '', $init = TRUE) {
2300
  if (is_array($var)) {
2301
    if (empty($var)) {
2302
      $output = 'array()';
2303
    }
2304
    else {
2305
      $output = "array(\n";
2306
      foreach ($var as $key => $value) {
2307
        $output .= "  " . views_var_export($key, '', FALSE) . " => " . views_var_export($value, '  ', FALSE) . ",\n";
2308
      }
2309
      $output .= ')';
2310
    }
2311
  }
2312
  elseif (is_bool($var)) {
2313
    $output = $var ? 'TRUE' : 'FALSE';
2314
  }
2315
  elseif (is_string($var) && strpos($var, "\n") !== FALSE) {
2316
    // Replace line breaks in strings with a token for replacement
2317
    // at the very end. This protects multi-line strings from
2318
    // unintentional indentation.
2319
    $var = str_replace("\n", "***BREAK***", $var);
2320
    $output = var_export($var, TRUE);
2321
  }
2322
  else {
2323
    $output = var_export($var, TRUE);
2324
  }
2325

    
2326
  if ($prefix) {
2327
    $output = str_replace("\n", "\n$prefix", $output);
2328
  }
2329

    
2330
  if ($init) {
2331
    $output = str_replace("***BREAK***", "\n", $output);
2332
  }
2333

    
2334
  return $output;
2335
}
2336

    
2337
/**
2338
 * Prepare a string for use as a valid CSS identifier (element, class or ID name).
2339
 * This function is similar to a core version but with more sane filter values.
2340
 *
2341
 * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
2342
 * CSS identifiers (including element names, classes, and IDs in selectors.)
2343
 *
2344
 * @param $identifier
2345
 *   The identifier to clean.
2346
 * @param $filter
2347
 *   An array of string replacements to use on the identifier.
2348
 * @return
2349
 *   The cleaned identifier.
2350
 *
2351
 * @see drupal_clean_css_identifier()
2352
 */
2353
function views_clean_css_identifier($identifier, $filter = array(' ' => '-', '/' => '-', '[' => '-', ']' => '')) {
2354
  // By default, we filter using Drupal's coding standards.
2355
  $identifier = strtr($identifier, $filter);
2356

    
2357
  // Valid characters in a CSS identifier are:
2358
  // - the hyphen (U+002D)
2359
  // - a-z (U+0030 - U+0039)
2360
  // - A-Z (U+0041 - U+005A)
2361
  // - the underscore (U+005F)
2362
  // - 0-9 (U+0061 - U+007A)
2363
  // - ISO 10646 characters U+00A1 and higher
2364
  // We strip out any character not in the above list.
2365
  $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);
2366

    
2367
  return $identifier;
2368
}
2369

    
2370
/**
2371
 * Implement hook_views_exportables().
2372
 */
2373
function views_views_exportables($op = 'list', $views = NULL, $name = 'foo') {
2374
  $all_views = views_get_all_views();
2375
  if ($op == 'list') {
2376

    
2377
    foreach ($all_views as $name => $view) {
2378
      // in list, $views is a list of tags.
2379
      if (empty($views) || in_array($view->tag, $views)) {
2380
        $return[$name] = array(
2381
          'name' => check_plain($name),
2382
          'desc' => check_plain($view->description),
2383
          'tag' => check_plain($view->tag)
2384
        );
2385
      }
2386
    }
2387
    return $return;
2388
  }
2389

    
2390
  if ($op == 'export') {
2391
    $code = "/**\n";
2392
    $code .= " * Implement hook_views_default_views().\n";
2393
    $code .= " */\n";
2394
    $code .= "function " . $name . "_views_default_views() {\n";
2395
    foreach ($views as $view => $truth) {
2396
      $code .= "  /*\n";
2397
      $code .= "   * View " . var_export($all_views[$view]->name, TRUE) . "\n";
2398
      $code .= "   */\n";
2399
      $code .= $all_views[$view]->export('  ');
2400
      $code .= '  $views[$view->name] = $view;' . "\n\n";
2401
    }
2402
    $code .= "  return \$views;\n";
2403
    $code .= "}\n";
2404

    
2405
    return $code;
2406
  }
2407
}
2408

    
2409
/**
2410
 * #process callback to see if we need to check_plain() the options.
2411
 *
2412
 * Since FAPI is inconsistent, the #options are sanitized for you in all cases
2413
 * _except_ checkboxes. We have form elements that are sometimes 'select' and
2414
 * sometimes 'checkboxes', so we need decide late in the form rendering cycle
2415
 * if the options need to be sanitized before they're rendered. This callback
2416
 * inspects the type, and if it's still 'checkboxes', does the sanitation.
2417
 */
2418
function views_process_check_options($element, &$form_state) {
2419
  if ($element['#type'] == 'checkboxes' || $element['#type'] == 'checkbox') {
2420
    $element['#options'] = array_map('check_plain', $element['#options']);
2421
  }
2422
  return $element;
2423
}
2424

    
2425
/**
2426
 * Trim the field down to the specified length.
2427
 *
2428
 * @param $alter
2429
 *   - max_length: Maximum length of the string, the rest gets truncated.
2430
 *   - word_boundary: Trim only on a word boundary.
2431
 *   - ellipsis: Show an ellipsis (...) at the end of the trimmed string.
2432
 *   - html: Take sure that the html is correct.
2433
 *
2434
 * @param $value
2435
 *   The string which should be trimmed.
2436
 */
2437
function views_trim_text($alter, $value) {
2438
  if (drupal_strlen($value) > $alter['max_length']) {
2439
    $value = drupal_substr($value, 0, $alter['max_length']);
2440
    // TODO: replace this with cleanstring of ctools
2441
    if (!empty($alter['word_boundary'])) {
2442
      $regex = "(.*)\b.+";
2443
      if (function_exists('mb_ereg')) {
2444
        mb_regex_encoding('UTF-8');
2445
        $found = mb_ereg($regex, $value, $matches);
2446
      }
2447
      else {
2448
        $found = preg_match("/$regex/us", $value, $matches);
2449
      }
2450
      if ($found) {
2451
        $value = $matches[1];
2452
      }
2453
    }
2454
    // Remove scraps of HTML entities from the end of a strings
2455
    $value = rtrim(preg_replace('/(?:<(?!.+>)|&(?!.+;)).*$/us', '', $value));
2456

    
2457
    if (!empty($alter['ellipsis'])) {
2458
      $value .= t('...');
2459
    }
2460
  }
2461
  if (!empty($alter['html'])) {
2462
    $value = _filter_htmlcorrector($value);
2463
  }
2464

    
2465
  return $value;
2466
}
2467

    
2468
/**
2469
 * Adds one to each key of the array.
2470
 *
2471
 * For example array(0 => 'foo') would be array(1 => 'foo').
2472
 */
2473
function views_array_key_plus($array) {
2474
  $keys = array_keys($array);
2475
  rsort($keys);
2476
  foreach ($keys as $key) {
2477
    $array[$key+1] = $array[$key];
2478
    unset($array[$key]);
2479
  }
2480
  asort($array);
2481
  return $array;
2482
}
2483

    
2484
/**
2485
 * Report to CTools that we use hook_views_api instead of hook_ctools_plugin_api()
2486
 */
2487
function views_ctools_plugin_api_hook_name() {
2488
  return 'views_api';
2489
}
2490

    
2491
// Declare API compatibility on behalf of core modules:
2492

    
2493
/**
2494
 * Implements hook_views_api().
2495
 *
2496
 * This one is used as the base to reduce errors when updating.
2497
 */
2498
function views_views_api() {
2499
  return array(
2500
    // in your modules do *not* use views_api_version()!!!
2501
    'api' => views_api_version(),
2502
    'path' => drupal_get_path('module', 'views') . '/modules',
2503
  );
2504
}
2505

    
2506
if (!function_exists('aggregator_views_api')) {
2507
  function aggregator_views_api() { return views_views_api(); }
2508
}
2509

    
2510
if (!function_exists('book_views_api')) {
2511
  function book_views_api() { return views_views_api(); }
2512
}
2513

    
2514
if (!function_exists('comment_views_api')) {
2515
  function comment_views_api() { return views_views_api(); }
2516
}
2517

    
2518
if (!function_exists('field_views_api')) {
2519
  function field_views_api() { return views_views_api(); }
2520
}
2521

    
2522
if (!function_exists('file_views_api')) {
2523
  function file_views_api() { return views_views_api(); }
2524
}
2525

    
2526
if (!function_exists('filter_views_api')) {
2527
  function filter_views_api() { return views_views_api(); }
2528
}
2529

    
2530
if (!function_exists('image_views_api')) {
2531
  function image_views_api() { return views_views_api(); }
2532
}
2533

    
2534
if (!function_exists('locale_views_api')) {
2535
  function locale_views_api() { return views_views_api(); }
2536
}
2537

    
2538
if (!function_exists('node_views_api')) {
2539
  function node_views_api() { return views_views_api(); }
2540
}
2541

    
2542
if (!function_exists('poll_views_api')) {
2543
  function poll_views_api() { return views_views_api(); }
2544
}
2545

    
2546
if (!function_exists('profile_views_api')) {
2547
  function profile_views_api() { return views_views_api(); }
2548
}
2549

    
2550
if (!function_exists('search_views_api')) {
2551
  function search_views_api() { return views_views_api(); }
2552
}
2553

    
2554
if (!function_exists('statistics_views_api')) {
2555
  function statistics_views_api() { return views_views_api(); }
2556
}
2557

    
2558
if (!function_exists('system_views_api')) {
2559
  function system_views_api() { return views_views_api(); }
2560
}
2561

    
2562
if (!function_exists('tracker_views_api')) {
2563
  function tracker_views_api() { return views_views_api(); }
2564
}
2565

    
2566
if (!function_exists('taxonomy_views_api')) {
2567
  function taxonomy_views_api() { return views_views_api(); }
2568
}
2569

    
2570
if (!function_exists('translation_views_api')) {
2571
  function translation_views_api() { return views_views_api(); }
2572
}
2573

    
2574
if (!function_exists('user_views_api')) {
2575
  function user_views_api() { return views_views_api(); }
2576
}
2577

    
2578
if (!function_exists('contact_views_api')) {
2579
  function contact_views_api() { return views_views_api(); }
2580
}