views.module 32.5 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\Html;
13
use Drupal\Component\Utility\SafeMarkup;
14
use Drupal\Core\Cache\Cache;
15
use Drupal\Core\Database\Query\AlterableInterface;
16
use Drupal\Core\Form\FormStateInterface;
17
use Drupal\Core\Render\Element;
18
use Drupal\Core\Routing\RouteMatchInterface;
19
use Drupal\Core\Url;
20
use Drupal\views\Plugin\Derivative\ViewsLocalTask;
21
use Drupal\Core\Template\AttributeArray;
22
use Drupal\views\ViewExecutable;
23
use Drupal\Component\Plugin\Exception\PluginException;
24
use Drupal\views\Entity\View;
25
use Drupal\views\Views;
26
use Drupal\field\FieldConfigInterface;
27

28
29
30
/**
 * Implements hook_help().
 */
31
function views_help($route_name, RouteMatchInterface $route_match) {
32
33
  switch ($route_name) {
    case 'help.page.views':
34
35
36
37
38
39
40
41
42
43
44
      $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>';
45
46
      $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>';
47
48
49
50
51
      $output .= '</dl>';
      return $output;
  }
}

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)) {
58
    $view->element['#attached']['drupalSettings']['views'] = array(
59
60
61
62
63
      'ajax_path' => \Drupal::url('views.ajax'),
      'ajaxViews' => array(
        'views_dom_id:' . $view->dom_id => array(
          'view_name' => $view->storage->id(),
          'view_display_id' => $view->current_display,
64
65
          'view_args' => SafeMarkup::checkPlain(implode('/', $view->args)),
          'view_path' => SafeMarkup::checkPlain(Url::fromRoute('<current>')->toString()),
66
67
68
69
70
          '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,
71
72
73
74
75
76
77
78
79
        ),
      ),
    );
    $view->element['#attached']['library'][] = 'views/views.ajax';
  }

  return $view;
}

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

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

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

  $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.
103
104
105
106
107
108
109
110
111
    'display' => array(
      'view_array' => array(),
      'view' => NULL,
      'rows' => array(),
      'header' => array(),
      'footer' => array(),
      'empty' => array(),
      'exposed' => array(),
      'more' => array(),
112
      'feed_icons' => array(),
113
114
115
116
117
      'pager' => array(),
      'title' => '',
      'attachment_before' => array(),
      'attachment_after' => array(),
    ),
merlinofchaos's avatar
merlinofchaos committed
118
119
120
121
122
    '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,
123
      'tags' => array(), 'quantity' => 9, 'element' => 0, 'parameters' => array()
merlinofchaos's avatar
merlinofchaos committed
124
125
126
127
128
129
    ),
  );

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

136
  $plugins = Views::getPluginDefinitions();
137
  $module_handler = \Drupal::moduleHandler();
merlinofchaos's avatar
merlinofchaos committed
138

139
140
141
142
  // 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
143
  foreach ($plugins as $type => $info) {
144
    foreach ($info as $def) {
145
146
147
148
149
      // 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;
      }
150
151
152
      // 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.
153
      // @todo: watchdog or exception?
154
      if (!isset($def['provider']) || !$module_handler->moduleExists($def['provider'])) {
155
156
        continue;
      }
merlinofchaos's avatar
merlinofchaos committed
157

158
159
160
      $hooks[$def['theme']] = array(
        'variables' => $variables[$type],
      );
merlinofchaos's avatar
merlinofchaos committed
161

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

166
167
168
169
170
171
172
173
174
      // 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';
        }
      }
175
      // The theme_file definition is always relative to the modules directory.
176
      elseif (!empty($def['theme_file'])) {
177
178
        $hooks[$def['theme']]['file'] = $def['theme_file'];
      }
179
180

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

      // 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.
194
      if (!function_exists('theme_' . $def['theme'])) {
195
        $hooks[$def['theme']]['path'] .= '/templates';
196
        $hooks[$def['theme']]['template'] = Html::cleanCssIdentifier($def['theme']);
197
198
199
      }
      else {
        $hooks[$def['theme']]['function'] = 'theme_' . $def['theme'];
200
      }
merlinofchaos's avatar
merlinofchaos committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    }
  }

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

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

  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.
 */
223
function views_preprocess_node(&$variables) {
224
225
  // The 'view' attribute of the node is added in
  // \Drupal\views\Plugin\views\row\EntityRow::preRender().
226
227
  if (!empty($variables['node']->view) && $variables['node']->view->storage->id()) {
    $variables['view'] = $variables['node']->view;
228
229
230
231
232
233
234
    // 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
235
236
237
238
    }
  }
}

239
240
241
242
243
244
245
246
247
248
249
250
251
/**
 * 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
252
253
254
255
/**
 * A theme preprocess function to automatically allow view-based node
 * templates if called from a view.
 */
256
function views_preprocess_comment(&$variables) {
257
258
  // The view data is added to the comment in
  // \Drupal\views\Plugin\views\row\EntityRow::preRender().
259
  if (!empty($variables['comment']->view) && $variables['comment']->view->storage->id()) {
260
    $variables['view'] = $variables['comment']->view;
261
262
263
264
265
266
267
268
269
270
271
272
  }
}

/**
 * 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
273
274
275
276
    }
  }
}

277
278
279
280
/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function views_theme_suggestions_container_alter(array &$suggestions, array $variables) {
281
  if (!empty($variables['element']['#type']) && $variables['element']['#type'] == 'more_link' && !empty($variables['element']['#view']) && $variables['element']['#view'] instanceof ViewExecutable) {
282
283
284
285
    $suggestions = array_merge($suggestions, $variables['element']['#view']->buildThemeFunctions('container__more_link'));
  }
}

merlinofchaos's avatar
merlinofchaos committed
286
/**
287
288
289
290
291
292
293
294
295
296
297
 * Implements hook_element_info_alter().
 *
 * @see views_page_display_pre_render()
 * @see views_preprocess_page()
 */
function views_element_info_alter(&$types) {
  $types['page']['#pre_render'][] = 'views_page_display_pre_render';
}

/**
 * #pre_render callback to set contextual links for views using a Page display.
merlinofchaos's avatar
merlinofchaos committed
298
 */
299
function views_page_display_pre_render(array $element) {
merlinofchaos's avatar
merlinofchaos committed
300
301
302
  // 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.
303
  if ($view = views_get_page_view()) {
304
    views_add_contextual_links($element, 'page', $view, $view->current_display);
merlinofchaos's avatar
merlinofchaos committed
305
  }
306
  return $element;
merlinofchaos's avatar
merlinofchaos committed
307
308
309
310
311
}

/**
 * Implements MODULE_preprocess_HOOK().
 */
312
function views_preprocess_html(&$variables) {
313
  // Early-return to prevent adding unnecessary JavaScript.
314
  if (!\Drupal::moduleHandler()->moduleExists('contextual') || !\Drupal::currentUser()->hasPermission('access contextual links')) {
315
316
317
    return;
  }

merlinofchaos's avatar
merlinofchaos committed
318
  // If the page contains a view as its main content, contextual links may have
319
320
  // been attached to the page as a whole; for example, by
  // views_page_display_pre_render().
merlinofchaos's avatar
merlinofchaos committed
321
322
323
324
325
326
327
  // 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
328
329
  // 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
330
  // JavaScript that will insert it back in the correct place.
331
332
  if (!empty($variables['page']['#views_contextual_links'])) {
    $variables['attributes']['data-views-page-contextual-id'] = _contextual_links_to_id($variables['page']['#contextual_links']);
merlinofchaos's avatar
merlinofchaos committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
  }
}

/**
 * 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
347
348
349
 * '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
350
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
 *
 * 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
385
386
387
 *   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
388
389
390
391
392
 * @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.
 *
393
 * @see \Drupal\views\Plugin\block\block\ViewsBlock::addContextualLinks()
394
 * @see views_preprocess_page()
merlinofchaos's avatar
merlinofchaos committed
395
396
 * @see template_preprocess_views_view()
 */
397
function views_add_contextual_links(&$render_element, $location, ViewExecutable $view, $display_id) {
merlinofchaos's avatar
merlinofchaos committed
398
  // Do not do anything if the view is configured to hide its administrative
399
400
  // links or if the Contextual Links module is not enabled.
  if (\Drupal::moduleHandler()->moduleExists('contextual') && $view->getShowAdminLinks()) {
merlinofchaos's avatar
merlinofchaos committed
401
402
403
    // 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.
404
    $plugin_id = $view->displayHandlers->get($display_id)->getPluginId();
405
    $plugin = Views::pluginManager('display')->getDefinition($plugin_id);
406
    // If contextual_links_locations are not set, provide a sane default. (To
merlinofchaos's avatar
merlinofchaos committed
407
    // avoid displaying any contextual links at all, a display plugin can still
408
    // set 'contextual_links_locations' to, e.g., {""}.)
409
410
411

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

merlinofchaos's avatar
merlinofchaos committed
420
    // On exposed_forms blocks contextual links should always be visible.
421
    $plugin['contextual_links_locations'][] = 'exposed_filter';
422
423
    $has_links = !empty($plugin['contextual links']) && !empty($plugin['contextual_links_locations']);
    if ($has_links && in_array($location, $plugin['contextual_links_locations'])) {
424
      foreach ($plugin['contextual links'] as $group => $link) {
merlinofchaos's avatar
merlinofchaos committed
425
426
        $args = array();
        $valid = TRUE;
427
428
        if (!empty($link['route_parameters_names'])) {
          foreach ($link['route_parameters_names'] as $parameter_name => $property) {
merlinofchaos's avatar
merlinofchaos committed
429
            // If the plugin is trying to create an invalid contextual link
430
431
432
433
            // (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
434
435
436
437
              $valid = FALSE;
              break;
            }
            else {
438
              $args[$parameter_name] = $view->storage->get($property);
merlinofchaos's avatar
merlinofchaos committed
439
440
441
442
443
444
            }
          }
        }
        // If the link was valid, attach information about it to the renderable
        // array.
        if ($valid) {
445
          $render_element['#views_contextual_links'] = TRUE;
446
447
448
          $render_element['#contextual_links'][$group] = array(
            'route_parameters' => $args,
            'metadata' => array(
449
450
451
              'location' => $location,
              'name' => $view->storage->id(),
              'display_id' => $display_id,
452
            ),
merlinofchaos's avatar
merlinofchaos committed
453
          );
454
455
456
457
458
459
          // If we're setting contextual links on a page, for a page view, for a
          // user that may use contextual links, attach Views' contextual links
          // JavaScript.
          if ($location === 'page' && $render_element['#type'] === 'page' && \Drupal::currentUser()->hasPermission('access contextual links')) {
            $render_element['#attached']['library'][] = 'views/views.contextual-links';
          }
merlinofchaos's avatar
merlinofchaos committed
460
461
462
463
464
465
        }
      }
    }
  }
}

466
/**
467
 * Implements hook_ENTITY_TYPE_create() for 'field_config'.
468
 */
469
function views_field_config_create(FieldConfigInterface $field) {
470
  Views::viewsData()->clear();
471
472
}

merlinofchaos's avatar
merlinofchaos committed
473
/**
474
 * Implements hook_ENTITY_TYPE_update() for 'field_config'.
merlinofchaos's avatar
merlinofchaos committed
475
 */
476
function views_field_config_update(FieldConfigInterface $field) {
477
  Views::viewsData()->clear();
merlinofchaos's avatar
merlinofchaos committed
478
479
480
}

/**
481
 * Implements hook_ENTITY_TYPE_delete() for 'field_config'.
merlinofchaos's avatar
merlinofchaos committed
482
 */
483
function views_field_config_delete(FieldConfigInterface $field) {
484
  Views::viewsData()->clear();
merlinofchaos's avatar
merlinofchaos committed
485
486
487
488
489
490
}

/**
 * Invalidate the views cache, forcing a rebuild on the next grab of table data.
 */
function views_invalidate_cache() {
491
  // Set the menu as needed to be rebuilt.
492
  \Drupal::service('router.builder')->setRebuildNeeded();
493

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

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

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

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

/**
 * 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;
}

/**
522
 * Find out what, if any, page view is currently in use.
merlinofchaos's avatar
merlinofchaos committed
523
 *
524
525
526
527
 * 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
528
529
530
531
532
533
534
535
536
537
 *   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
 *
538
 * @return \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
539
540
541
542
543
544
545
546
547
548
549
 */
function &views_set_current_view($view = NULL) {
  static $cache = NULL;
  if (isset($view)) {
    $cache = $view;
  }

  return $cache;
}

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

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

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

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

582
583
584
585
586
587
588
589
590
591
592
593
  $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'));
594

595
596
597
598
599
600
601
  $hooks['field_views_data'] = array(
    'group' => 'views',
  );
  $hooks['field_views_data_alter'] = array(
    'group' => 'views',
  );

602
  return $hooks;
merlinofchaos's avatar
merlinofchaos committed
603
604
605
}

/**
dawehner's avatar
dawehner committed
606
607
 * Returns whether the view is enabled.
 *
608
 * @param \Drupal\views\Entity\View $view
xjm's avatar
xjm committed
609
 *   The view object to check.
dawehner's avatar
dawehner committed
610
611
612
 *
 * @return bool
 *   Returns TRUE if a view is enabled, FALSE otherwise.
merlinofchaos's avatar
merlinofchaos committed
613
 */
614
function views_view_is_enabled(View $view) {
615
  return $view->status();
merlinofchaos's avatar
merlinofchaos committed
616
617
618
}

/**
dawehner's avatar
dawehner committed
619
620
 * Returns whether the view is disabled.
 *
621
 * @param \Drupal\views\Entity\View $view
xjm's avatar
xjm committed
622
 *   The view object to check.
dawehner's avatar
dawehner committed
623
624
625
 *
 * @return bool
 *   Returns TRUE if a view is disabled, FALSE otherwise.
merlinofchaos's avatar
merlinofchaos committed
626
 */
627
function views_view_is_disabled(View $view) {
628
  return !$view->status();
merlinofchaos's avatar
merlinofchaos committed
629
630
}

631
/**
632
 * Enables and saves a view.
633
 *
634
 * @param \Drupal\views\Entity\View $view
635
 *   The View object to disable.
636
 */
637
function views_enable_view(View $view) {
638
  $view->enable()->save();
639
640
641
}

/**
642
 * Disables and saves a view.
643
 *
644
 * @param \Drupal\views\Entity\View $view
645
 *   The View object to disable.
646
 */
647
function views_disable_view(View $view) {
648
  $view->disable()->save();
649
650
}

651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
/**
 * 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().
675
  $substitutions = \Drupal::moduleHandler()->invokeAll('views_form_substitutions');
676
677
678
679
680
681
  foreach ($substitutions as $placeholder => $substitution) {
    $search[] = $placeholder;
    $replace[] = $substitution;
  }

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

684
  // Sort, render and add remaining form fields.
685
  $children = Element::children($element, TRUE);
686
  $element['#children'] = drupal_render_children($element, $children);
687

688
689
690
  return $element;
}

merlinofchaos's avatar
merlinofchaos committed
691
/**
692
 * Implements hook_form_alter() for the exposed form.
merlinofchaos's avatar
merlinofchaos committed
693
694
695
696
 *
 * Since the exposed form is a GET form, we don't want it to send a wide
 * variety of information.
 */
697
function views_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
merlinofchaos's avatar
merlinofchaos committed
698
699
700
701
702
703
704
705
706
707
708
  $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().
 */
709
function views_query_views_alter(AlterableInterface $query) {
merlinofchaos's avatar
merlinofchaos committed
710
  $substitutions = $query->getMetaData('views_substitutions');
711
712
  $tables = &$query->getTables();
  $where = &$query->conditions();
merlinofchaos's avatar
merlinofchaos committed
713

714
  // Replaces substitutions in tables.
merlinofchaos's avatar
merlinofchaos committed
715
716
717
718
719
720
721
722
  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];
      }
    }
  }

723
  // Replaces substitutions in filter criteria.
merlinofchaos's avatar
merlinofchaos committed
724
725
726
727
728
729
  _views_query_tag_alter_condition($query, $where, $substitutions);
}

/**
 * Replaces the substitutions recursive foreach condition.
 */
730
function _views_query_tag_alter_condition(AlterableInterface $query, &$conditions, $substitutions) {
merlinofchaos's avatar
merlinofchaos committed
731
732
733
734
735
736
  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'])) {
737
        $sub_conditions = &$condition['field']->conditions();
merlinofchaos's avatar
merlinofchaos committed
738
739
740
        _views_query_tag_alter_condition($query, $sub_conditions, $substitutions);
      }
      // $condition['value'] is a subquery so alter the subquery recursive.
741
      // Therefore make sure to get the metadata of the main query.
merlinofchaos's avatar
merlinofchaos committed
742
743
744
745
746
747
      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'])) {
748
749
750
751
752
753
754
755
756
757
758
759
        // We can not use a simple str_replace() here because it always returns
        // a string and we have to keep the type of the condition value intact.
        if (is_array($condition['value'])) {
          foreach ($condition['value'] as &$value) {
            if (is_string($value)) {
              $value = str_replace(array_keys($substitutions), array_values($substitutions), $value);
            }
          }
        }
        elseif (is_string($condition['value'])) {
          $condition['value'] = str_replace(array_keys($substitutions), array_values($substitutions), $condition['value']);
        }
merlinofchaos's avatar
merlinofchaos committed
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
      }
    }
  }
}

/**
 * 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
775
 * loading the view, getting the preview and then getting $view->getTitle().
merlinofchaos's avatar
merlinofchaos committed
776
777
778
779
780
781
782
783
784
785
786
 *
 * @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();
787
788
  // Remove $name and $display_id from the arguments.
  unset($args[0], $args[1]);
merlinofchaos's avatar
merlinofchaos committed
789

790
  $view = Views::getView($name);
merlinofchaos's avatar
merlinofchaos committed
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
  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();
817
818
  // Remove $name and $display_id from the arguments.
  unset($args[0], $args[1]);
merlinofchaos's avatar
merlinofchaos committed
819

820
  $view = Views::getView($name);
merlinofchaos's avatar
merlinofchaos committed
821
822
  if (is_object($view)) {
    if (is_array($args)) {
823
      $view->setArguments($args);
merlinofchaos's avatar
merlinofchaos committed
824
825
    }
    if (is_string($display_id)) {
826
      $view->setDisplay($display_id);
merlinofchaos's avatar
merlinofchaos committed
827
828
    }
    else {
829
      $view->initDisplay();
merlinofchaos's avatar
merlinofchaos committed
830
    }
831
    $view->preExecute();
merlinofchaos's avatar
merlinofchaos committed
832
833
834
835
836
837
838
839
    $view->execute();
    return $view->result;
  }
  else {
    return array();
  }
}

840
841
842
/**
 * Validation callback for query tags.
 */
843
function views_element_validate_tags($element, FormStateInterface $form_state) {
844
845
846
  $values = array_map('trim', explode(',', $element['#value']));
  foreach ($values as $value) {
    if (preg_match("/[^a-z_]/", $value)) {
847
      $form_state->setError($element, t('The query tags may only contain lower-case alphabetical characters and underscores.'));
848
849
850
851
      return;
    }
  }
}
852
853
854
855
856
857
858
859
860

/**
 * 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);
}