views.module 34.7 KB
Newer Older
merlinofchaos's avatar
merlinofchaos committed
1
2
3
4
5
6
7
8
9
10
11
<?php

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

12
use Drupal\Component\Utility\String;
13
use Drupal\Core\Cache\Cache;
14
use Drupal\Core\Database\Query\AlterableInterface;
15
use Drupal\Core\Form\FormStateInterface;
16
use Drupal\Core\Render\Element;
17
use Drupal\Core\Routing\RouteMatchInterface;
18
use Drupal\views\Plugin\Derivative\ViewsLocalTask;
19
use Drupal\Core\Template\AttributeArray;
20
use Drupal\views\ViewExecutable;
21
use Drupal\Component\Plugin\Exception\PluginException;
22
use Drupal\views\Entity\View;
23
use Drupal\views\Views;
24
use Drupal\field\FieldConfigInterface;
25

26
27
28
/**
 * Implements hook_help().
 */
29
function views_help($route_name, RouteMatchInterface $route_match) {
30
31
  switch ($route_name) {
    case 'help.page.views':
32
33
34
35
36
37
38
39
40
41
42
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Views module provides a back end to fetch information from content, user accounts, taxonomy terms, and other entities from the database and present it to the user as a grid, HTML list, table, unformatted list, etc. The resulting displays are known generally as <em>views</em>.') . '</p>';
      $output .= '<p>' . t('For more information, see the <a href="!views">online documentation for the Views module</a>.', array('!views' => 'https://drupal.org/documentation/modules/views')) . '</p>';
      $output .= '<p>' . t('In order to create and modify your own views using the administration and configuration user interface, you will need to enable either the Views UI module in core or a contributed module that provides a user interface for Views. See the <a href="!views-ui">Views UI module help page</a> for more information.', array('!views-ui' => \Drupal::url('help.page', array('name' => 'views_ui')))) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Adding functionality to administrative pages') . '</dt>';
      $output .= '<dd>' . t('The Views module adds functionality to some core administration pages. For example, <em>admin/content</em> uses Views to filter and sort content. With Views uninstalled, <em>admin/content</em> is more limited.') . '</dd>';
      $output .= '<dt>' . t('Expanding Views functionality') . '</dt>';
      $output .= '<dd>' . t('Contributed projects that support the Views module can be found in the <a href="!node">online documentation for Views-related contributed modules</a>.', array('!node' => 'https://drupal.org/documentation/modules/views/add-ons')) . '</dd>';
43
44
      $output .= '<dt>' . t('Improving table accessibility') . '</dt>';
      $output .= '<dd>' . t('Views tables include semantic markup to improve accessibility. Data cells are automatically associated with header cells through id and header attributes. To improve the accessibility of your tables you can add descriptive elements within the Views table settings. The <em>caption</em> element can introduce context for a table, making it easier to understand. The <em>summary</em> element can provide an overview of how the data has been organized and how to navigate the table. Both the caption and summary are visible by default and also implemented according to HTML5 guidelines.') . '</dd>';
45
46
47
48
49
      $output .= '</dl>';
      return $output;
  }
}

50
51
52
53
54
55
56
57
/**
 * Implements hook_views_pre_render().
 */
function views_views_pre_render($view) {
  // If using AJAX, send identifying data about this view.
  if ($view->ajaxEnabled() && empty($view->is_attachment) && empty($view->live_preview)) {
    $settings = array(
      'views' => array(
58
        'ajax_path' => \Drupal::url('views.ajax'),
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
        'ajaxViews' => array(
          'views_dom_id:' . $view->dom_id => array(
            'view_name' => $view->storage->id(),
            'view_display_id' => $view->current_display,
            'view_args' => String::checkPlain(implode('/', $view->args)),
            'view_path' => String::checkPlain(current_path()),
            'view_base_path' => $view->getPath(),
            'view_dom_id' => $view->dom_id,
            // To fit multiple views on a page, the programmer may have
            // overridden the display's pager_element.
            'pager_element' => isset($view->pager) ? $view->pager->getPagerId() : 0,
          ),
        ),
      ),
    );
    $view->element['#attached']['js'][] = array('type' => 'setting', 'data' => $settings);
    $view->element['#attached']['library'][] = 'views/views.ajax';
  }

  return $view;
}

merlinofchaos's avatar
merlinofchaos committed
81
/**
82
83
84
85
 * Implements hook_theme().
 *
 * Register views theming functions and those that are defined via views plugin
 * definitions.
merlinofchaos's avatar
merlinofchaos committed
86
87
 */
function views_theme($existing, $type, $theme, $path) {
88
  \Drupal::moduleHandler()->loadInclude('views', 'inc', 'views.theme');
merlinofchaos's avatar
merlinofchaos committed
89
90
91

  // Some quasi clever array merging here.
  $base = array(
92
    'file' => 'views.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
93
94
95
96
  );

  // Our extra version of pager from pager.inc
  $hooks['views_mini_pager'] = $base + array(
97
    'variables' => array('tags' => array(), 'quantity' => 9, 'element' => 0, 'parameters' => array()),
98
    'template' => 'views-mini-pager',
merlinofchaos's avatar
merlinofchaos committed
99
100
101
102
103
104
  );

  $variables = array(
    // For displays, we pass in a dummy array as the first parameter, since
    // $view is an object but the core contextual_preprocess() function only
    // attaches contextual links when the primary theme argument is an array.
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
    'display' => array(
      'view_array' => array(),
      'view' => NULL,
      'rows' => array(),
      'header' => array(),
      'footer' => array(),
      'empty' => array(),
      'exposed' => array(),
      'more' => array(),
      'feed_icon' => array(),
      'pager' => array(),
      'title' => '',
      'attachment_before' => array(),
      'attachment_after' => array(),
    ),
merlinofchaos's avatar
merlinofchaos committed
120
121
122
123
124
    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
    'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL, 'field_alias' => NULL),
    'exposed_form' => array('view' => NULL, 'options' => NULL),
    'pager' => array(
      'view' => NULL, 'options' => NULL,
125
      'tags' => array(), 'quantity' => 9, 'element' => 0, 'parameters' => array()
merlinofchaos's avatar
merlinofchaos committed
126
127
128
129
130
131
    ),
  );

  // Default view themes
  $hooks['views_view_field'] = $base + array(
    'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL),
132
    'function' => 'theme_views_view_field',
merlinofchaos's avatar
merlinofchaos committed
133
134
135
  );
  $hooks['views_view_grouping'] = $base + array(
    'variables' => array('view' => NULL, 'grouping' => NULL, 'grouping_level' => NULL, 'rows' => NULL, 'title' => NULL),
136
    'template' => 'views-view-grouping',
merlinofchaos's avatar
merlinofchaos committed
137
138
  );

139
  $plugins = Views::getPluginDefinitions();
140
  $module_handler = \Drupal::moduleHandler();
merlinofchaos's avatar
merlinofchaos committed
141

142
143
144
145
  // Register theme functions for all style plugins. It provides a basic auto
  // implementation of theme functions or template files by using the plugin
  // definitions (theme, theme_file, module, register_theme). Template files are
  // assumed to be located in the templates folder.
merlinofchaos's avatar
merlinofchaos committed
146
  foreach ($plugins as $type => $info) {
147
    foreach ($info as $def) {
148
149
150
151
152
      // Not all plugins have theme functions, and they can also explicitly
      // prevent a theme function from being registered automatically.
      if (!isset($def['theme']) || empty($def['register_theme'])) {
        continue;
      }
153
154
155
      // For each theme registration, we have a base directory to check for the
      // templates folder. This will be relative to the root of the given module
      // folder, so we always need a module definition.
156
      // @todo: watchdog or exception?
157
      if (!isset($def['provider']) || !$module_handler->moduleExists($def['provider'])) {
158
159
        continue;
      }
merlinofchaos's avatar
merlinofchaos committed
160

161
162
163
      $hooks[$def['theme']] = array(
        'variables' => $variables[$type],
      );
merlinofchaos's avatar
merlinofchaos committed
164

165
166
      // We always use the module directory as base dir.
      $module_dir = drupal_get_path('module', $def['provider']);
167
      $hooks[$def['theme']]['path'] = $module_dir;
merlinofchaos's avatar
merlinofchaos committed
168

169
170
171
172
173
174
175
176
177
      // For the views module we ensure views.theme.inc is included.
      if ($def['provider'] == 'views') {
        if (!isset($hooks[$def['theme']]['includes'])) {
          $hooks[$def['theme']]['includes'] = array();
        }
        if (!in_array('views.theme.inc', $hooks[$def['theme']]['includes'])) {
          $hooks[$def['theme']]['includes'][] = $module_dir . '/views.theme.inc';
        }
      }
178
      // The theme_file definition is always relative to the modules directory.
179
      elseif (!empty($def['theme_file'])) {
180
181
        $hooks[$def['theme']]['file'] = $def['theme_file'];
      }
182
183

      // Whenever we have a theme file, we include it directly so we can
184
185
      // auto-detect the theme function.
      if (isset($def['theme_file'])) {
186
        $include = DRUPAL_ROOT . '/' . $module_dir . '/' . $def['theme_file'];
187
188
        if (is_file($include)) {
          require_once $include;
merlinofchaos's avatar
merlinofchaos committed
189
190
        }
      }
191
192
193
194
195
196

      // If there is no theme function for the given theme definition, it must
      // be a template file. By default this file is located in the /templates
      // directory of the module's folder. If a module wants to define its own
      // location it has to set register_theme of the plugin to FALSE and
      // implement hook_theme() by itself.
197
      if (!function_exists('theme_' . $def['theme'])) {
198
199
200
201
202
        $hooks[$def['theme']]['path'] .= '/templates';
        $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']);
      }
      else {
        $hooks[$def['theme']]['function'] = 'theme_' . $def['theme'];
203
      }
merlinofchaos's avatar
merlinofchaos committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    }
  }

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

  $hooks['views_exposed_form'] = $base + array(
    'template' => 'views-exposed-form',
    'render element' => 'form',
  );

  $hooks['views_more'] = $base + array(
    'variables' => array('more_url' => NULL, 'link_text' => 'more', 'view' => NULL),
218
    'template' => 'views-more',
merlinofchaos's avatar
merlinofchaos committed
219
220
221
222
223
224
225
226
227
228
229
230
231
  );

  return $hooks;
}

/**
 * A theme preprocess function to automatically allow view-based node
 * templates if called from a view.
 *
 * The 'modules/node.views.inc' file is a better place for this, but
 * we haven't got a chance to load that file before Drupal builds the
 * node portion of the theme registry.
 */
232
function views_preprocess_node(&$variables) {
233
  \Drupal::moduleHandler()->loadInclude('node', 'views.inc');
234
235
  // The 'view' attribute of the node is added in
  // \Drupal\views\Plugin\views\row\EntityRow::preRender().
236
237
  if (!empty($variables['node']->view) && $variables['node']->view->storage->id()) {
    $variables['view'] = $variables['node']->view;
238
239
240
241
242
243
244
    // If a node is being rendered in a view, and the view does not have a path,
    // prevent drupal from accidentally setting the $page variable:
    if (!empty($variables['view']->current_display)
        && $variables['page']
        && $variables['view_mode'] == 'full'
        && !$variables['view']->display_handler->hasPath()) {
      $variables['page'] = FALSE;
merlinofchaos's avatar
merlinofchaos committed
245
246
247
248
    }
  }
}

249
250
251
252
253
254
255
256
257
258
259
260
261
/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function views_theme_suggestions_node_alter(array &$suggestions, array $variables) {
  $node = $variables['elements']['#node'];
  if (!empty($node->view) && $node->view->storage->id()) {
    $suggestions[] = 'node__view__' . $node->view->storage->id();
    if (!empty($node->view->current_display)) {
      $suggestions[] = 'node__view__' . $node->view->storage->id() . '__' . $node->view->current_display;
    }
  }
}

merlinofchaos's avatar
merlinofchaos committed
262
263
264
265
/**
 * A theme preprocess function to automatically allow view-based node
 * templates if called from a view.
 */
266
function views_preprocess_comment(&$variables) {
267
268
  // The view data is added to the comment in
  // \Drupal\views\Plugin\views\row\EntityRow::preRender().
269
  if (!empty($variables['comment']->view) && $variables['comment']->view->storage->id()) {
270
    $variables['view'] = $variables['comment']->view;
271
272
273
274
275
276
277
278
279
280
281
282
  }
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function views_theme_suggestions_comment_alter(array &$suggestions, array $variables) {
  $comment = $variables['elements']['#comment'];
  if (!empty($comment->view) && $comment->view->storage->id()) {
    $suggestions[] = 'comment__view__' . $comment->view->storage->id();
    if (!empty($comment->view->current_display)) {
      $suggestions[] = 'comment__view__' . $comment->view->storage->id() . '__' . $comment->view->current_display;
merlinofchaos's avatar
merlinofchaos committed
283
284
285
286
287
288
289
290
291
292
293
    }
  }
}

/**
 * Implements hook_page_alter().
 */
function views_page_alter(&$page) {
  // If the main content of this page contains a view, attach its contextual
  // links to the overall page array. This allows them to be rendered directly
  // next to the page title.
294
  if ($view = views_get_page_view()) {
merlinofchaos's avatar
merlinofchaos committed
295
296
297
298
299
300
301
    views_add_contextual_links($page, 'page', $view, $view->current_display);
  }
}

/**
 * Implements MODULE_preprocess_HOOK().
 */
302
function views_preprocess_page(&$variables) {
303
  // Early-return to prevent adding unnecessary JavaScript.
304
  if (!\Drupal::currentUser()->hasPermission('access contextual links')) {
305
306
307
    return;
  }

merlinofchaos's avatar
merlinofchaos committed
308
309
310
311
312
313
314
315
316
  // If the page contains a view as its main content, contextual links may have
  // been attached to the page as a whole; for example, by views_page_alter().
  // This allows them to be associated with the page and rendered by default
  // next to the page title (which we want). However, it also causes the
  // Contextual Links module to treat the wrapper for the entire page (i.e.,
  // the <body> tag) as the HTML element that these contextual links are
  // associated with. This we don't want; for better visual highlighting, we
  // prefer a smaller region to be chosen. The region we prefer differs from
  // theme to theme and depends on the details of the theme's markup in
317
318
  // page.html.twig, so we can only find it using JavaScript. We therefore
  // remove the "contextual-region" class from the <body> tag here and add
merlinofchaos's avatar
merlinofchaos committed
319
  // JavaScript that will insert it back in the correct place.
320
  if (!empty($variables['page']['#views_contextual_links']) && isset($variables['attributes']['class'])) {
321
322
323
324
325
326
    /** @var \Drupal\Core\Page\HtmlPage $page_object */
    $page_object = $variables['page']['#page'];
    $attributes = $page_object->getBodyAttributes();
    $class = $attributes['class'] ?: array();

    $key = array_search('contextual-region', $variables['attributes']['class'] instanceof AttributeArray ? $variables['attributes']['class']->value() : $variables['attributes']['class']);
merlinofchaos's avatar
merlinofchaos committed
327
    if ($key !== FALSE) {
328
329
330
331
      /** @var \Drupal\Core\Page\HtmlPage $page_object */
      unset($class[$key]);
      $attributes['class'] = $class;
      $attributes['data-views-page-contextual-id'] = $variables['title_suffix']['contextual_links']['#id'];
332
      $variables['#attached']['library'][] = 'views/views.contextual-links';
merlinofchaos's avatar
merlinofchaos committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
    }
  }
}

/**
 * Adds contextual links associated with a view display to a renderable array.
 *
 * This function should be called when a view is being rendered in a particular
 * location and you want to attach the appropriate contextual links (e.g.,
 * links for editing the view) to it.
 *
 * The function operates by checking the view's display plugin to see if it has
 * defined any contextual links that are intended to be displayed in the
 * requested location; if so, it attaches them. The contextual links intended
 * for a particular location are defined by the 'contextual links' and
348
349
350
 * 'contextual_links_locations' properties in the plugin annotation; as a
 * result, these hook implementations have full control over where and how
 * contextual links are rendered for each display.
merlinofchaos's avatar
merlinofchaos committed
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
 *
 * In addition to attaching the contextual links to the passed-in array (via
 * the standard #contextual_links property), this function also attaches
 * additional information via the #views_contextual_links_info property. This
 * stores an array whose keys are the names of each module that provided
 * views-related contextual links (same as the keys of the #contextual_links
 * array itself) and whose values are themselves arrays whose keys ('location',
 * 'view_name', and 'view_display_id') store the location, name of the view,
 * and display ID that were passed in to this function. This allows you to
 * access information about the contextual links and how they were generated in
 * a variety of contexts where you might be manipulating the renderable array
 * later on (for example, alter hooks which run later during the same page
 * request).
 *
 * @param $render_element
 *   The renderable array to which contextual links will be added. This array
 *   should be suitable for passing in to drupal_render() and will normally
 *   contain a representation of the view display whose contextual links are
 *   being requested.
 * @param $location
 *   The location in which the calling function intends to render the view and
 *   its contextual links. The core system supports three options for this
 *   parameter:
 *   - 'block': Used when rendering a block which contains a view. This
 *     retrieves any contextual links intended to be attached to the block
 *     itself.
 *   - 'page': Used when rendering the main content of a page which contains a
 *     view. This retrieves any contextual links intended to be attached to the
 *     page itself (for example, links which are displayed directly next to the
 *     page title).
 *   - 'view': Used when rendering the view itself, in any context. This
 *     retrieves any contextual links intended to be attached directly to the
 *     view.
 *   If you are rendering a view and its contextual links in another location,
 *   you can pass in a different value for this parameter. However, you will
386
387
388
 *   also need to set 'contextual_links_locations' in your plugin annotation to
 *   indicate which view displays support having their contextual links
 *   rendered in the location you have defined.
merlinofchaos's avatar
merlinofchaos committed
389
390
391
392
393
 * @param $view
 *   The view whose contextual links will be added.
 * @param $display_id
 *   The ID of the display within $view whose contextual links will be added.
 *
394
 * @see \Drupal\views\Plugin\block\block\ViewsBlock::addContextualLinks()
merlinofchaos's avatar
merlinofchaos committed
395
396
397
 * @see views_page_alter()
 * @see template_preprocess_views_view()
 */
398
function views_add_contextual_links(&$render_element, $location, ViewExecutable $view, $display_id) {
merlinofchaos's avatar
merlinofchaos committed
399
400
  // Do not do anything if the view is configured to hide its administrative
  // links.
401
  if ($view->getShowAdminLinks()) {
merlinofchaos's avatar
merlinofchaos committed
402
403
404
    // Also do not do anything if the display plugin has not defined any
    // contextual links that are intended to be displayed in the requested
    // location.
405
    $plugin_id = $view->displayHandlers->get($display_id)->getPluginId();
406
    $plugin = Views::pluginManager('display')->getDefinition($plugin_id);
407
    // If contextual_links_locations are not set, provide a sane default. (To
merlinofchaos's avatar
merlinofchaos committed
408
    // avoid displaying any contextual links at all, a display plugin can still
409
    // set 'contextual_links_locations' to, e.g., {""}.)
410
411
412

    if (!isset($plugin['contextual_links_locations'])) {
      $plugin['contextual_links_locations'] = array('view');
413
    }
414
    elseif ($plugin['contextual_links_locations'] == array() || $plugin['contextual_links_locations'] == array('')) {
415
416
      $plugin['contextual_links_locations'] = array();
    }
417
418
419
420
    else {
      $plugin += array('contextual_links_locations' => array('view'));
    }

merlinofchaos's avatar
merlinofchaos committed
421
    // On exposed_forms blocks contextual links should always be visible.
422
    $plugin['contextual_links_locations'][] = 'exposed_filter';
423
424
    $has_links = !empty($plugin['contextual links']) && !empty($plugin['contextual_links_locations']);
    if ($has_links && in_array($location, $plugin['contextual_links_locations'])) {
425
      foreach ($plugin['contextual links'] as $group => $link) {
merlinofchaos's avatar
merlinofchaos committed
426
427
        $args = array();
        $valid = TRUE;
428
429
        if (!empty($link['route_parameters_names'])) {
          foreach ($link['route_parameters_names'] as $parameter_name => $property) {
merlinofchaos's avatar
merlinofchaos committed
430
            // If the plugin is trying to create an invalid contextual link
431
432
433
434
            // (for example, "path/to/{$view->storage->property}", where
            // $view->storage->{property} does not exist), we cannot construct
            // the link, so we skip it.
            if (!property_exists($view->storage, $property)) {
merlinofchaos's avatar
merlinofchaos committed
435
436
437
438
              $valid = FALSE;
              break;
            }
            else {
439
              $args[$parameter_name] = $view->storage->{$property};
merlinofchaos's avatar
merlinofchaos committed
440
441
442
443
444
445
            }
          }
        }
        // If the link was valid, attach information about it to the renderable
        // array.
        if ($valid) {
446
          $render_element['#views_contextual_links'] = TRUE;
447
448
449
          $render_element['#contextual_links'][$group] = array(
            'route_parameters' => $args,
            'metadata' => array(
450
451
452
              'location' => $location,
              'name' => $view->storage->id(),
              'display_id' => $display_id,
453
            ),
merlinofchaos's avatar
merlinofchaos committed
454
455
456
457
458
459
460
          );
        }
      }
    }
  }
}

461
/**
462
 * Implements hook_ENTITY_TYPE_create() for 'field_config'.
463
 */
464
function views_field_config_create(FieldConfigInterface $field) {
465
466
467
468
  // @todo: Is this necessary? Use cache tags to only delete Views' cache data?
  \Drupal::cache('discovery')->deleteAll();
}

merlinofchaos's avatar
merlinofchaos committed
469
/**
470
 * Implements hook_ENTITY_TYPE_update() for 'field_config'.
merlinofchaos's avatar
merlinofchaos committed
471
 */
472
function views_field_config_update(FieldConfigInterface $field) {
473
  Cache::deleteTags(array('extension' => 'views'));
474
  \Drupal::cache('render')->deleteAll();
merlinofchaos's avatar
merlinofchaos committed
475
476
477
}

/**
478
 * Implements hook_ENTITY_TYPE_delete() for 'field_config'.
merlinofchaos's avatar
merlinofchaos committed
479
 */
480
function views_field_config_delete(FieldConfigInterface $field) {
481
  Cache::deleteTags(array('extension' => 'views'));
482
  \Drupal::cache('render')->deleteAll();
merlinofchaos's avatar
merlinofchaos committed
483
484
485
486
487
488
}

/**
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
 */
function views_invalidate_cache() {
489
490
  // Clear Views' info cache entries.
  Cache::deleteTags(array('extension' => 'views'));
491
492

  // Set the menu as needed to be rebuilt.
493
  \Drupal::service('router.builder')->setRebuildNeeded();
494

495
  $module_handler = \Drupal::moduleHandler();
496

497
498
499
  // Reset the RouteSubscriber from views.
  \Drupal::getContainer()->get('views.route_subscriber')->reset();

500
  // Invalidate the block cache to update views block derivatives.
501
  if ($module_handler->moduleExists('block')) {
502
    \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
503
504
  }

505
  // Allow modules to respond to the Views cache being cleared.
506
  $module_handler->invokeAll('views_invalidate_cache');
merlinofchaos's avatar
merlinofchaos committed
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
}

/**
 * Set the current 'page view' that is being displayed so that it is easy
 * for other modules or the theme to identify.
 */
function &views_set_page_view($view = NULL) {
  static $cache = NULL;
  if (isset($view)) {
    $cache = $view;
  }

  return $cache;
}

/**
523
 * Find out what, if any, page view is currently in use.
merlinofchaos's avatar
merlinofchaos committed
524
 *
525
526
527
528
 * Note that this returns a reference, so be careful! You can unintentionally
 * modify the $view object.
 *
 * @return \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
529
530
531
532
533
534
535
536
537
538
 *   A fully formed, empty $view object.
 */
function &views_get_page_view() {
  return views_set_page_view();
}

/**
 * Set the current 'current view' that is being built/rendered so that it is
 * easy for other modules or items in drupal_eval to identify
 *
539
 * @return \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
540
541
542
543
544
545
546
547
548
549
550
 */
function &views_set_current_view($view = NULL) {
  static $cache = NULL;
  if (isset($view)) {
    $cache = $view;
  }

  return $cache;
}

/**
551
 * Find out what, if any, current view is currently in use.
merlinofchaos's avatar
merlinofchaos committed
552
 *
553
554
555
556
 * Note that this returns a reference, so be careful! You can unintentionally
 * modify the $view object.
 *
 * @return \Drupal\views\ViewExecutable
557
 *   The current view object.
merlinofchaos's avatar
merlinofchaos committed
558
559
560
561
562
 */
function &views_get_current_view() {
  return views_set_current_view();
}

563
564
565
566
/**
 * Implements hook_hook_info().
 */
function views_hook_info() {
567
568
569
570
571
572
573
574
575
  $hooks = array();

  $hooks += array_fill_keys(array(
    'views_data',
    'views_data_alter',
    'views_analyze',
    'views_invalidate_cache',
  ), array('group' => 'views'));

576
577
578
579
580
581
582
  // Register a views_plugins alter hook for all plugin types.
  foreach (ViewExecutable::getPluginTypes() as $type) {
    $hooks['views_plugins_' . $type . '_alter'] = array(
      'group' => 'views',
    );
  }

583
584
585
586
587
588
589
590
591
592
593
594
  $hooks += array_fill_keys(array(
    'views_query_substitutions',
    'views_form_substitutions',
    'views_pre_view',
    'views_pre_build',
    'views_post_build',
    'views_pre_execute',
    'views_post_execute',
    'views_pre_render',
    'views_post_render',
    'views_query_alter',
  ), array('group' => 'views_execution'));
595
596

  return $hooks;
merlinofchaos's avatar
merlinofchaos committed
597
598
}

599
600
/**
 * Get enabled display extenders.
601
 *
602
603
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\views\Views::getEnabledDisplayExtenders().
604
605
 */
function views_get_enabled_display_extenders() {
606
  return Views::getEnabledDisplayExtenders();
607
608
}

merlinofchaos's avatar
merlinofchaos committed
609
610
611
612
/**
 * Return a list of all views and display IDs that have a particular
 * setting in their display's plugin settings.
 *
613
 * @param string $type
614
 *   A flag from the display plugin definitions (e.g, 'uses_menu_links').
615
616
617
 *
 * @return array
 *   A list of arrays containing the $view and $display_id.
merlinofchaos's avatar
merlinofchaos committed
618
619
620
621
622
623
 * @code
 * array(
 *   array($view, $display_id),
 *   array($view, $display_id),
 * );
 * @endcode
624
 *
625
626
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\views\Views::getApplicableViews().
merlinofchaos's avatar
merlinofchaos committed
627
628
 */
function views_get_applicable_views($type) {
629
  return Views::getApplicableViews($type);
merlinofchaos's avatar
merlinofchaos committed
630
631
632
}

/**
633
 * Returns an array of all views as fully loaded $view objects.
634
 *
635
636
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\views\Views::getAllViews().
merlinofchaos's avatar
merlinofchaos committed
637
 */
638
function views_get_all_views() {
639
  return Views::getAllViews();
640
641
}

merlinofchaos's avatar
merlinofchaos committed
642
643
/**
 * Returns an array of all enabled views, as fully loaded $view objects.
644
 *
645
646
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\views\Views::getEnabledViews().
merlinofchaos's avatar
merlinofchaos committed
647
648
 */
function views_get_enabled_views() {
649
  return Views::getEnabledViews();
merlinofchaos's avatar
merlinofchaos committed
650
651
652
653
}

/**
 * Returns an array of all disabled views, as fully loaded $view objects.
654
 *
655
656
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\views\Views::getDisabledViews().
merlinofchaos's avatar
merlinofchaos committed
657
658
 */
function views_get_disabled_views() {
659
  return Views::getDisabledViews();
merlinofchaos's avatar
merlinofchaos committed
660
661
662
663
664
665
666
667
668
669
670
671
672
673
}

/**
 * Return an array of view as options array, that can be used by select,
 * checkboxes and radios as #options.
 *
 * @param bool $views_only
 *  If TRUE, only return views, not displays.
 * @param string $filter
 *  Filters the views on status. Can either be 'all' (default), 'enabled' or
 *  'disabled'
 * @param  mixed $exclude_view
 *  view or current display to exclude
 *  either a
674
 *  - views object (containing $exclude_view->storage->name and $exclude_view->current_display)
merlinofchaos's avatar
merlinofchaos committed
675
676
677
678
679
 *  - views name as string:  e.g. my_view
 *  - views name and display id (separated by ':'): e.g. my_view:default
 * @param bool $optgroup
 *  If TRUE, returns an array with optgroups for each view (will be ignored for
 *  $views_only = TRUE). Can be used by select
680
681
 * @param bool $sort
 *  If TRUE, the list of views is sorted ascending.
merlinofchaos's avatar
merlinofchaos committed
682
683
684
685
 *
 * @return array
 *  an associative array for use in select.
 *  - key: view name and display id separated by ':', or the view name only
686
 *
687
688
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\views\Views::getViewsAsOptions().
merlinofchaos's avatar
merlinofchaos committed
689
 */
690
function views_get_views_as_options($views_only = FALSE, $filter = 'all', $exclude_view = NULL, $optgroup = FALSE, $sort = FALSE) {
691
  return Views::getViewsAsOptions($views_only, $filter, $exclude_view, $optgroup, $sort);
merlinofchaos's avatar
merlinofchaos committed
692
693
694
}

/**
dawehner's avatar
dawehner committed
695
696
 * Returns whether the view is enabled.
 *
697
 * @param \Drupal\views\Entity\View $view
xjm's avatar
xjm committed
698
 *   The view object to check.
dawehner's avatar
dawehner committed
699
700
701
 *
 * @return bool
 *   Returns TRUE if a view is enabled, FALSE otherwise.
merlinofchaos's avatar
merlinofchaos committed
702
 */
703
function views_view_is_enabled(View $view) {
704
  return $view->status();
merlinofchaos's avatar
merlinofchaos committed
705
706
707
}

/**
dawehner's avatar
dawehner committed
708
709
 * Returns whether the view is disabled.
 *
710
 * @param \Drupal\views\Entity\View $view
xjm's avatar
xjm committed
711
 *   The view object to check.
dawehner's avatar
dawehner committed
712
713
714
 *
 * @return bool
 *   Returns TRUE if a view is disabled, FALSE otherwise.
merlinofchaos's avatar
merlinofchaos committed
715
 */
716
function views_view_is_disabled(View $view) {
717
  return !$view->status();
merlinofchaos's avatar
merlinofchaos committed
718
719
}

720
/**
721
 * Enables and saves a view.
722
 *
723
 * @param \Drupal\views\Entity\View $view
724
 *   The View object to disable.
725
 */
726
function views_enable_view(View $view) {
727
  $view->enable()->save();
728
729
730
}

/**
731
 * Disables and saves a view.
732
 *
733
 * @param \Drupal\views\Entity\View $view
734
 *   The View object to disable.
735
 */
736
function views_disable_view(View $view) {
737
  $view->disable()->save();
738
739
}

740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
/**
 * Replaces views substitution placeholders.
 *
 * @param array $element
 *   An associative array containing the properties of the element.
 *   Properties used: #substitutions, #children.
 * @return array
 *   The $element with prepared variables ready for #theme 'form'
 *   in views_form_views_form.
 */
function views_pre_render_views_form_views_form($element) {
  // Placeholders and their substitutions (usually rendered form elements).
  $search = array();
  $replace = array();

  // Add in substitutions provided by the form.
  foreach ($element['#substitutions']['#value'] as $substitution) {
    $field_name = $substitution['field_name'];
    $row_id = $substitution['row_id'];

    $search[] = $substitution['placeholder'];
    $replace[] = isset($element[$field_name][$row_id]) ? drupal_render($element[$field_name][$row_id]) : '';
  }
  // Add in substitutions from hook_views_form_substitutions().
764
  $substitutions = \Drupal::moduleHandler()->invokeAll('views_form_substitutions');
765
766
767
768
769
770
  foreach ($substitutions as $placeholder => $substitution) {
    $search[] = $placeholder;
    $replace[] = $substitution;
  }

  // Apply substitutions to the rendered output.
771
  $element['output'] = array('#markup' => str_replace($search, $replace, drupal_render($element['output'])));
772

773
  // Sort, render and add remaining form fields.
774
  $children = Element::children($element, TRUE);
775
  $element['#children'] = drupal_render_children($element, $children);
776

777
778
779
  return $element;
}

merlinofchaos's avatar
merlinofchaos committed
780
781
782
783
784
785
/**
 * Implement hook_form_alter for the exposed form.
 *
 * Since the exposed form is a GET form, we don't want it to send a wide
 * variety of information.
 */
786
function views_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
merlinofchaos's avatar
merlinofchaos committed
787
788
789
790
791
792
793
794
795
796
797
  $form['form_build_id']['#access'] = FALSE;
  $form['form_token']['#access'] = FALSE;
  $form['form_id']['#access'] = FALSE;
}

/**
 * Implements hook_query_TAG_alter().
 *
 * This is the hook_query_alter() for queries tagged by Views and is used to
 * add in substitutions from hook_views_query_substitutions().
 */
798
function views_query_views_alter(AlterableInterface $query) {
merlinofchaos's avatar
merlinofchaos committed
799
  $substitutions = $query->getMetaData('views_substitutions');
800
801
  $tables = &$query->getTables();
  $where = &$query->conditions();
merlinofchaos's avatar
merlinofchaos committed
802
803
804
805
806
807
808
809
810
811

  // Replaces substitions in tables.
  foreach ($tables as $table_name => $table_metadata) {
    foreach ($table_metadata['arguments'] as $replacement_key => $value) {
      if (isset($substitutions[$value])) {
        $tables[$table_name]['arguments'][$replacement_key] = $substitutions[$value];
      }
    }
  }

812
  // Replaces substitions in filter criteria.
merlinofchaos's avatar
merlinofchaos committed
813
814
815
816
817
818
  _views_query_tag_alter_condition($query, $where, $substitutions);
}

/**
 * Replaces the substitutions recursive foreach condition.
 */
819
function _views_query_tag_alter_condition(AlterableInterface $query, &$conditions, $substitutions) {
merlinofchaos's avatar
merlinofchaos committed
820
821
822
823
824
825
  foreach ($conditions as $condition_id => &$condition) {
    if (is_numeric($condition_id)) {
      if (is_string($condition['field'])) {
        $condition['field'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['field']);
      }
      elseif (is_object($condition['field'])) {
826
        $sub_conditions = &$condition['field']->conditions();
merlinofchaos's avatar
merlinofchaos committed
827
828
829
        _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
      }
      // $condition['value'] is a subquery so alter the subquery recursive.
830
      // Therefore make sure to get the metadata of the main query.
merlinofchaos's avatar
merlinofchaos committed
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
      if (is_object($condition['value'])) {
        $subquery = $condition['value'];
        $subquery->addMetaData('views_substitutions', $query->getMetaData('views_substitutions'));
        views_query_views_alter($condition['value']);
      }
      elseif (isset($condition['value'])) {
        $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
      }
    }
  }
}

/**
 * Embed a view using a PHP snippet.
 *
 * This function is meant to be called from PHP snippets, should one wish to
 * embed a view in a node or something. It's meant to provide the simplest
 * solution and doesn't really offer a lot of options, but breaking the function
 * apart is pretty easy, and this provides a worthwhile guide to doing so.
 *
 * Note that this function does NOT display the title of the view. If you want
 * to do that, you will need to do what this function does manually, by
853
 * loading the view, getting the preview and then getting $view->getTitle().
merlinofchaos's avatar
merlinofchaos committed
854
855
856
857
858
859
860
861
862
863
864
 *
 * @param $name
 *   The name of the view to embed.
 * @param $display_id
 *   The display id to embed. If unsure, use 'default', as it will always be
 *   valid. But things like 'page' or 'block' should work here.
 * @param ...
 *   Any additional parameters will be passed as arguments.
 */
function views_embed_view($name, $display_id = 'default') {
  $args = func_get_args();
865
866
  // Remove $name and $display_id from the arguments.
  unset($args[0], $args[1]);
merlinofchaos's avatar
merlinofchaos committed
867

868
  $view = Views::getView($name);
merlinofchaos's avatar
merlinofchaos committed
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
  if (!$view || !$view->access($display_id)) {
    return;
  }

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

/**
 * Get the result of a view.
 *
 * @param string $name
 *   The name of the view to retrieve the data from.
 * @param string $display_id
 *   The display id. On the edit page for the view in question, you'll find
 *   a list of displays at the left side of the control area. "Master"
 *   will be at the top of that list. Hover your cursor over the name of the
 *   display you want to use. An URL will appear in the status bar of your
 *   browser. This is usually at the bottom of the window, in the chrome.
 *   Everything after #views-tab- is the display ID, e.g. page_1.
 * @param ...
 *   Any additional parameters will be passed as arguments.
 * @return array
 *   An array containing an object for each view item.
 */
function views_get_view_result($name, $display_id = NULL) {
  $args = func_get_args();
895
896
  // Remove $name and $display_id from the arguments.
  unset($args[0], $args[1]);
merlinofchaos's avatar
merlinofchaos committed
897

898
  $view = Views::getView($name);
merlinofchaos's avatar
merlinofchaos committed
899
900
  if (is_object($view)) {
    if (is_array($args)) {
901
      $view->setArguments($args);
merlinofchaos's avatar
merlinofchaos committed
902
903
    }
    if (is_string($display_id)) {
904
      $view->setDisplay($display_id);
merlinofchaos's avatar
merlinofchaos committed
905
906
    }
    else {
907
      $view->initDisplay();
merlinofchaos's avatar
merlinofchaos committed
908
    }
909
    $view->preExecute();
merlinofchaos's avatar
merlinofchaos committed
910
911
912
913
914
915
916
917
    $view->execute();
    return $view->result;
  }
  else {
    return array();
  }
}

918
919
920
/**
 * Validation callback for query tags.
 */
921
function views_element_validate_tags($element, FormStateInterface $form_state) {
922
923
924
  $values = array_map('trim', explode(',', $element['#value']));
  foreach ($values as $value) {
    if (preg_match("/[^a-z_]/", $value)) {
925
      $form_state->setError($element, t('The query tags may only contain lower-case alphabetical characters and underscores.'));
926
927
928
929
      return;
    }
  }
}
930
931
932
933
934
935
936
937
938

/**
 * Implements hook_local_tasks_alter().
 */
function views_local_tasks_alter(&$local_tasks) {
  $container = \Drupal::getContainer();
  $local_task = ViewsLocalTask::create($container, 'views_view');
  $local_task->alterLocalTasks($local_tasks);
}