views.api.php 44.9 KB
Newer Older
merlinofchaos's avatar
merlinofchaos committed
1 2 3 4
<?php

/**
 * @file
5
 * Describes hooks and plugins provided by the Views module.
merlinofchaos's avatar
merlinofchaos committed
6 7
 */

8
use Drupal\Core\Language\LanguageInterface;
9
use Drupal\views\Plugin\views\cache\CachePluginBase;
10
use Drupal\views\Plugin\views\PluginBase;
11
use Drupal\views\ViewExecutable;
12

13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/**
 * @defgroup views_overview Views overview
 * @{
 * Overview of the Views module API
 *
 * The Views module is a generalized query and display engine, which can be used
 * to make views (formatted lists, grids, feeds, and other output) of items
 * (often entities, but can be other types of data). Developers can interact
 * with Views in several ways:
 * - Provide plugins: Views plugins govern nearly every aspect of views,
 *   including querying (sorting, filtering, etc.) and display (at several
 *   levels of granularity, ranging from the entire view to the details of a
 *   field). See the @link views_plugins Views plugins topic @endlink for
 *   more information.
 * - Provide data: Data types can be provided to Views by implementing
28 29 30 31
 *   hook_views_data(), and data types provided by other modules can be altered
 *   by implementing hook_views_data_alter(). To provide views data for an
 *   entity, create a class implementing
 *   \Drupal\views\EntityViewsDataInterface and reference this in the
32
 *   "views_data" annotation in the entity class. You can autogenerate big parts
33
 *   of the integration if you extend the \Drupal\views\EntityViewsData base
34 35
 *   class. See the @link entity_api Entity API topic @endlink for more
 *   information about entities.
36
 * - Implement hooks: A few operations in Views can be influenced by hooks.
37
 *   See the @link views_hooks Views hooks topic @endlink for a list.
38 39 40 41
 * - Theming: See the @link views_templates Views templates topic @endlink
 *   for more information.
 *
 * @see \Drupal\views\ViewExecutable
42
 * @see \Drupal\views\Views
43 44 45
 * @}
 */

merlinofchaos's avatar
merlinofchaos committed
46
/**
47
 * @defgroup views_plugins Views plugins
48
 * @{
49
 * Overview of views plugins
merlinofchaos's avatar
merlinofchaos committed
50
 *
51
 * Views plugins are objects that are used to build and render the view.
52 53 54 55
 * See individual views plugin topics for more information about the
 * specifics of each plugin type, and the
 * @link plugin_api Plugin API topic @endlink for more information about
 * plugins in general.
merlinofchaos's avatar
merlinofchaos committed
56
 *
57 58 59
 * Some Views plugins are known as handlers. Handler plugins help build the
 * view query object: filtering, contextual filtering, sorting, relationships,
 * etc.
60
 *
61 62
 * @todo Document specific options on the appropriate plugin base classes.
 * @todo Add examples.
merlinofchaos's avatar
merlinofchaos committed
63
 *
64
 * @ingroup views_overview
65 66
 * @see \Drupal\views\Plugin\views\PluginBase
 * @see \Drupal\views\Plugin\views\HandlerBase
67 68
 * @see plugin_api
 * @see annotation
69
 * @}
merlinofchaos's avatar
merlinofchaos committed
70 71 72 73 74
 */

/**
 * @defgroup views_hooks Views hooks
 * @{
75
 * Hooks that allow other modules to implement the Views API.
76 77
 * @ingroup views_overview
 * @}
merlinofchaos's avatar
merlinofchaos committed
78 79
 */

80 81 82 83 84
/**
 * @addtogroup hooks
 * @{
 */

85 86 87 88 89 90 91 92 93 94 95
/**
 * Analyze a view to provide warnings about its configuration.
 *
 * @param \Drupal\views\ViewExecutable $view
 *   The view being executed.
 *
 * @return array
 *   Array of warning messages built by Analyzer::formatMessage to be displayed
 *   to the user following analysis of the view.
 */
function hook_views_analyze(Drupal\views\ViewExecutable $view) {
96
  $messages = [];
97 98 99 100 101 102 103 104

  if ($view->display_handler->options['pager']['type'] == 'none') {
    $messages[] = Drupal\views\Analyzer::formatMessage(t('This view has no pager. This could cause performance issues when the view contains many items.'), 'warning');
  }

  return $messages;
}

merlinofchaos's avatar
merlinofchaos committed
105
/**
106 107 108 109
 * Describe data tables and fields (or the equivalent) to Views.
 *
 * The table and fields are processed in Views using various plugins. See
 * the @link views_plugins Views plugins topic @endlink for more information.
110
 *
111 112 113
 * To provide views data for an entity, instead of implementing this hook,
 * create a class implementing \Drupal\views\EntityViewsDataInterface and
 * reference this in the "views" annotation in the entity class. The return
114 115 116 117
 * value of the getViewsData() method on the interface is the same as this hook,
 * and base class in \Drupal\views\EntityViewsData will take care of adding the
 * basic Views tables and fields for your entity. See the
 * @link entity_api Entity API topic @endlink for more information about
118 119
 * entities.
 *
120
 * The data described with this hook is fetched and retrieved by
121
 * \Drupal\views\Views::viewsData()->get().
122
 *
123
 * @return array
124 125 126 127 128 129 130 131
 *   An associative array describing the structure of database tables and fields
 *   (and their equivalents) provided for use in Views. At the outermost level,
 *   the keys are the names used internally by Views for the tables (usually the
 *   actual table name). Each table's array describes the table itself, how to
 *   join to other tables, and the fields that are part of the table. The sample
 *   function body provides documentation of the details.
 *
 * @see hook_views_data_alter()
merlinofchaos's avatar
merlinofchaos committed
132 133
 */
function hook_views_data() {
134 135
  // This example describes how to write hook_views_data() for a table defined
  // like this:
merlinofchaos's avatar
merlinofchaos committed
136
  // CREATE TABLE example_table (
137
  //   nid INT(11) NOT NULL         COMMENT 'Primary key: {node}.nid.',
merlinofchaos's avatar
merlinofchaos committed
138 139 140 141
  //   plain_text_field VARCHAR(32) COMMENT 'Just a plain text field.',
  //   numeric_field INT(11)        COMMENT 'Just a numeric field.',
  //   boolean_field INT(1)         COMMENT 'Just an on/off field.',
  //   timestamp_field INT(8)       COMMENT 'Just a timestamp field.',
142
  //   langcode VARCHAR(12)         COMMENT 'Language code field.',
merlinofchaos's avatar
merlinofchaos committed
143 144 145
  //   PRIMARY KEY(nid)
  // );

146
  // Define the return array.
147
  $data = [];
merlinofchaos's avatar
merlinofchaos committed
148

149 150
  // The outermost keys of $data are Views table names, which should usually
  // be the same as the hook_schema() table names.
151
  $data['example_table'] = [];
152 153 154

  // The value corresponding to key 'table' gives properties of the table
  // itself.
155
  $data['example_table']['table'] = [];
156 157 158 159

  // Within 'table', the value of 'group' (translated string) is used as a
  // prefix in Views UI for this table's fields, filters, etc. When adding
  // a field, filter, etc. you can also filter by the group.
merlinofchaos's avatar
merlinofchaos committed
160 161
  $data['example_table']['table']['group'] = t('Example table');

162 163 164 165 166 167
  // Within 'table', the value of 'provider' is the module that provides schema
  // or the entity type that causes the table to exist. Setting this ensures
  // that views have the correct dependencies. This is automatically set to the
  // module that implements hook_views_data().
  $data['example_table']['table']['provider'] = 'example_module';

168 169 170 171
  // Some tables are "base" tables, meaning that they can be the base tables
  // for views. Non-base tables can only be brought in via relationships in
  // views based on other tables. To define a table to be a base table, add
  // key 'base' to the 'table' array:
172
  $data['example_table']['table']['base'] = [
173 174 175
    // Identifier (primary) field in this table for Views.
    'field' => 'nid',
    // Label in the UI.
merlinofchaos's avatar
merlinofchaos committed
176
    'title' => t('Example table'),
177
    // Longer description in the UI. Required.
merlinofchaos's avatar
merlinofchaos committed
178 179
    'help' => t('Example table contains example content and can be related to nodes.'),
    'weight' => -10,
180
  ];
merlinofchaos's avatar
merlinofchaos committed
181

182 183 184 185 186 187 188 189 190 191
  // Some tables have an implicit, automatic relationship to other tables,
  // meaning that when the other table is available in a view (either as the
  // base table or through a relationship), this table's fields, filters, etc.
  // are automatically made available without having to add an additional
  // relationship. To define an implicit relationship that will make your
  // table automatically available when another table is present, add a 'join'
  // section to your 'table' section. Note that it is usually only a good idea
  // to do this for one-to-one joins, because otherwise your automatic join
  // will add more rows to the view. It is also not a good idea to do this if
  // most views won't need your table -- if that is the case, define a
192
  // relationship instead (see below).
193
  //
194 195 196 197 198
  // If you've decided an automatic join is a good idea, here's how to do it;
  // the resulting SQL query will look something like this:
  //   ... FROM example_table et ... JOIN node_field_data nfd
  //   ON et.nid = nfd.nid AND ('extra' clauses will be here) ...
  // although the table aliases will be different.
199
  $data['example_table']['table']['join'] = [
200
    // Within the 'join' section, list one or more tables to automatically
201 202 203 204 205 206
    // join to. In this example, every time 'node_field_data' is available in
    // a view, 'example_table' will be too. The array keys here are the array
    // keys for the other tables, given in their hook_views_data()
    // implementations. If the table listed here is from another module's
    // hook_views_data() implementation, make sure your module depends on that
    // other module.
207
    'node_field_data' => [
208
      // Primary key field in node_field_data to use in the join.
merlinofchaos's avatar
merlinofchaos committed
209
      'left_field' => 'nid',
210
      // Foreign key field in example_table to use in the join.
merlinofchaos's avatar
merlinofchaos committed
211
      'field' => 'nid',
212
      // 'extra' is an array of additional conditions on the join.
213 214
      'extra' => [
        0 => [
215
          // Adds AND node_field_data.published = TRUE to the join.
216 217
          'field' => 'published',
          'value' => TRUE,
218 219
        ],
        1 => [
220 221 222 223 224
          // Adds AND example_table.numeric_field = 1 to the join.
          'left_field' => 'numeric_field',
          'value' => 1,
          // If true, the value will not be surrounded in quotes.
          'numeric' => TRUE,
225 226
        ],
        2 => [
227 228
          // Adds AND example_table.boolean_field <>
          // node_field_data.published to the join.
229 230 231 232
          'field' => 'published',
          'left_field' => 'boolean_field',
          // The operator used, Defaults to "=".
          'operator' => '!=',
233 234 235 236
        ],
      ],
    ],
  ];
merlinofchaos's avatar
merlinofchaos committed
237

238 239 240 241 242 243 244 245 246 247 248 249 250
  // You can also do a more complex join, where in order to get to a certain
  // base table defined in a hook_views_data() implementation, you will join
  // to a different table that Views knows how to auto-join to the base table.
  // For instance, if another module that your module depends on had
  // defined a table 'foo' with an automatic join to 'node_field_table' (as
  // shown above), you could join to 'node_field_table' via the 'foo' table.
  // Here's how to do this, and the resulting SQL query would look something
  // like this:
  //   ... FROM example_table et ... JOIN foo foo
  //   ON et.nid = foo.nid AND ('extra' clauses will be here) ...
  //   JOIN node_field_data nfd ON (definition of the join from the foo
  //   module goes here) ...
  // although the table aliases will be different.
251
  $data['example_table']['table']['join']['node_field_data'] = [
252 253 254 255 256 257 258 259
    // 'node_field_data' above is the base we're joining to in Views.
    // 'left_table' is the table we're actually joining to, in order to get to
    // 'node_field_data'. It has to be something that Views knows how to join
    // to 'node_field_data'.
    'left_table' => 'foo',
    'left_field' => 'nid',
    'field' => 'nid',
    // 'extra' is an array of additional conditions on the join.
260
    'extra' => [
261 262
      // This syntax matches additional fields in the two tables:
      // ... AND foo.langcode = example_table.langcode ...
263
      ['left_field' => 'langcode', 'field' => 'langcode'],
264 265 266
      // This syntax adds a condition on our table. 'operator' defaults to
      // '=' for non-array values, or 'IN' for array values.
      // ... AND example_table.numeric_field > 0 ...
267 268 269
      ['field' => 'numeric_field', 'value' => 0, 'numeric' => TRUE, 'operator' => '>'],
    ],
  ];
270

271 272 273 274
  // Other array elements at the top level of your table's array describe
  // individual database table fields made available to Views. The array keys
  // are the names (unique within the table) used by Views for the fields,
  // usually equal to the database field names.
merlinofchaos's avatar
merlinofchaos committed
275
  //
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
  // Each field entry must have the following elements:
  // - title: Translated label for the field in the UI.
  // - help: Description of the field in the UI.
  //
  // Each field entry may also have one or more of the following elements,
  // describing "handlers" (plugins) for the field:
  // - relationship: Specifies a handler that allows this field to be used
  //   to define a relationship to another table in Views.
  // - field: Specifies a handler to make it available to Views as a field.
  // - filter: Specifies a handler to make it available to Views as a filter.
  // - sort: Specifies a handler to make it available to Views as a sort.
  // - argument: Specifies a handler to make it available to Views as an
  //   argument, or contextual filter as it is known in the UI.
  // - area: Specifies a handler to make it available to Views to add content
  //   to the header, footer, or as no result behavior.
  //
  // Note that when specifying handlers, you must give the handler plugin ID
  // and you may also specify overrides for various settings that make up the
  // plugin definition. See examples below; the Boolean example demonstrates
  // setting overrides.
merlinofchaos's avatar
merlinofchaos committed
296

297 298
  // Node ID field, exposed as relationship only, since it is a foreign key
  // in this table.
299
  $data['example_table']['nid'] = [
merlinofchaos's avatar
merlinofchaos committed
300
    'title' => t('Example content'),
301 302
    'help' => t('Relate example content to the node content'),

303 304
    // Define a relationship to the node_field_data table, so views whose
    // base table is example_table can add a relationship to nodes. To make a
305 306 307 308
    // relationship in the other direction, you can:
    // - Use hook_views_data_alter() -- see the function body example on that
    //   hook for details.
    // - Use the implicit join method described above.
309
    'relationship' => [
310
      // Views name of the table to join to for the relationship.
311
      'base' => 'node_field_data',
312 313 314
      // Database field name in the other table to join on.
      'base field' => 'nid',
      // ID of relationship handler plugin to use.
aspilicious's avatar
aspilicious committed
315
      'id' => 'standard',
316
      // Default label for relationship in the UI.
dawehner's avatar
dawehner committed
317
      'label' => t('Example node'),
318 319
    ],
  ];
merlinofchaos's avatar
merlinofchaos committed
320

321
  // Plain text field, exposed as a field, sort, filter, and argument.
322
  $data['example_table']['plain_text_field'] = [
merlinofchaos's avatar
merlinofchaos committed
323 324
    'title' => t('Plain text field'),
    'help' => t('Just a plain text field.'),
325

326
    'field' => [
327
      // ID of field handler plugin to use.
aspilicious's avatar
aspilicious committed
328
      'id' => 'standard',
329
    ],
330

331
    'sort' => [
332
      // ID of sort handler plugin to use.
aspilicious's avatar
aspilicious committed
333
      'id' => 'standard',
334
    ],
335

336
    'filter' => [
337
      // ID of filter handler plugin to use.
aspilicious's avatar
aspilicious committed
338
      'id' => 'string',
339
    ],
340

341
    'argument' => [
342
      // ID of argument handler plugin to use.
aspilicious's avatar
aspilicious committed
343
      'id' => 'string',
344 345
    ],
  ];
merlinofchaos's avatar
merlinofchaos committed
346

347
  // Numeric field, exposed as a field, sort, filter, and argument.
348
  $data['example_table']['numeric_field'] = [
merlinofchaos's avatar
merlinofchaos committed
349 350
    'title' => t('Numeric field'),
    'help' => t('Just a numeric field.'),
351

352
    'field' => [
353
      // ID of field handler plugin to use.
aspilicious's avatar
aspilicious committed
354
      'id' => 'numeric',
355
    ],
356

357
    'sort' => [
358 359
      // ID of sort handler plugin to use.
      'id' => 'standard',
360
    ],
361

362
    'filter' => [
363
      // ID of filter handler plugin to use.
aspilicious's avatar
aspilicious committed
364
      'id' => 'numeric',
365
    ],
366

367
    'argument' => [
368 369
      // ID of argument handler plugin to use.
      'id' => 'numeric',
370 371
    ],
  ];
merlinofchaos's avatar
merlinofchaos committed
372

373 374
  // Boolean field, exposed as a field, sort, and filter. The filter section
  // illustrates overriding various settings.
375
  $data['example_table']['boolean_field'] = [
merlinofchaos's avatar
merlinofchaos committed
376 377
    'title' => t('Boolean field'),
    'help' => t('Just an on/off field.'),
378

379
    'field' => [
380
      // ID of field handler plugin to use.
aspilicious's avatar
aspilicious committed
381
      'id' => 'boolean',
382
    ],
383

384
    'sort' => [
385 386
      // ID of sort handler plugin to use.
      'id' => 'standard',
387
    ],
388

389
    'filter' => [
390
      // ID of filter handler plugin to use.
aspilicious's avatar
aspilicious committed
391
      'id' => 'boolean',
392 393
      // Override the generic field title, so that the filter uses a different
      // label in the UI.
merlinofchaos's avatar
merlinofchaos committed
394
      'label' => t('Published'),
395 396
      // Override the default BooleanOperator filter handler's 'type' setting,
      // to display this as a "Yes/No" filter instead of a "True/False" filter.
merlinofchaos's avatar
merlinofchaos committed
397
      'type' => 'yes-no',
398 399
      // Override the default Boolean filter handler's 'use_equal' setting, to
      // make the query use 'boolean_field = 1' instead of 'boolean_field <> 0'.
400
      'use_equal' => TRUE,
401 402
    ],
  ];
merlinofchaos's avatar
merlinofchaos committed
403

404
  // Integer timestamp field, exposed as a field, sort, and filter.
405
  $data['example_table']['timestamp_field'] = [
merlinofchaos's avatar
merlinofchaos committed
406 407
    'title' => t('Timestamp field'),
    'help' => t('Just a timestamp field.'),
408

409
    'field' => [
410
      // ID of field handler plugin to use.
aspilicious's avatar
aspilicious committed
411
      'id' => 'date',
412
    ],
413

414
    'sort' => [
415
      // ID of sort handler plugin to use.
aspilicious's avatar
aspilicious committed
416
      'id' => 'date',
417
    ],
418

419
    'filter' => [
420
      // ID of filter handler plugin to use.
aspilicious's avatar
aspilicious committed
421
      'id' => 'date',
422 423
    ],
  ];
merlinofchaos's avatar
merlinofchaos committed
424

425 426 427 428 429
  // Area example. Areas are not generally associated with actual data
  // tables and fields. This example is from views_views_data(), which defines
  // the "Global" table (not really a table, but a group of Fields, Filters,
  // etc. that are grouped into section "Global" in the UI). Here's the
  // definition of the generic "Text area":
430
  $data['views']['area'] = [
431 432
    'title' => t('Text area'),
    'help' => t('Provide markup text for the area.'),
433
    'area' => [
434 435
      // ID of the area handler plugin to use.
      'id' => 'text',
436 437
    ],
  ];
438

merlinofchaos's avatar
merlinofchaos committed
439 440 441 442
  return $data;
}

/**
443
 * Alter the table and field information from hook_views_data().
merlinofchaos's avatar
merlinofchaos committed
444
 *
445
 * @param array $data
446 447
 *   An array of all information about Views tables and fields, collected from
 *   hook_views_data(), passed by reference.
merlinofchaos's avatar
merlinofchaos committed
448 449 450
 *
 * @see hook_views_data()
 */
451
function hook_views_data_alter(array &$data) {
452 453
  // Alter the title of the node_field_data:nid field in the Views UI.
  $data['node_field_data']['nid']['title'] = t('Node-Nid');
merlinofchaos's avatar
merlinofchaos committed
454

455
  // Add an additional field to the users_field_data table.
456
  $data['users_field_data']['example_field'] = [
merlinofchaos's avatar
merlinofchaos committed
457 458
    'title' => t('Example field'),
    'help' => t('Some example content that references a user'),
459

460
    'field' => [
461
      // ID of the field handler to use.
462
      'id' => 'example_field',
463 464
    ],
  ];
merlinofchaos's avatar
merlinofchaos committed
465

466 467
  // Change the handler of the node title field, presumably to a handler plugin
  // you define in your module. Give the ID of this plugin.
468
  $data['node_field_data']['title']['field']['id'] = 'node_title';
merlinofchaos's avatar
merlinofchaos committed
469

470 471 472 473 474 475 476 477 478 479 480 481
  // Add a relationship that will allow a view whose base table is 'foo' (from
  // another module) to have a relationship to 'example_table' (from my module),
  // via joining foo.fid to example_table.eid.
  //
  // This relationship has to be added to the 'foo' Views data, which my module
  // does not control, so it must be done in hook_views_data_alter(), not
  // hook_views_data().
  //
  // In Views data definitions, each field can have only one relationship. So
  // rather than adding this relationship directly to the $data['foo']['fid']
  // field entry, which could overwrite an existing relationship, we define
  // a dummy field key to handle the relationship.
482
  $data['foo']['unique_dummy_name'] = [
483 484 485
    'title' => t('Title seen while adding relationship'),
    'help' => t('More information about the relationship'),

486
    'relationship' => [
487 488 489 490 491 492 493 494
      // Views name of the table being joined to from foo.
      'base' => 'example_table',
      // Database field name in example_table for the join.
      'base field' => 'eid',
      // Real database field name in foo for the join, to override
      // 'unique_dummy_name'.
      'field' => 'fid',
      // ID of relationship handler plugin to use.
aspilicious's avatar
aspilicious committed
495
      'id' => 'standard',
496
      'label' => t('Default label for relationship'),
497 498
    ],
  ];
499

merlinofchaos's avatar
merlinofchaos committed
500 501 502
  // Note that the $data array is not returned – it is modified by reference.
}

503 504 505 506
/**
 * Override the default Views data for a Field API field.
 *
 * The field module's implementation of hook_views_data() invokes this for each
507 508
 * field storage, in the module that defines the field type. It is not invoked
 * in other modules.
509 510
 *
 * If no hook implementation exists, hook_views_data() falls back to
511
 * views_field_default_views_data().
512
 *
513 514
 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
 *   The field storage config entity.
515 516 517 518 519
 *
 * @return array
 *   An array of views data, in the same format as the return value of
 *   hook_views_data().
 *
520
 * @see views_views_data()
521 522 523
 * @see hook_field_views_data_alter()
 * @see hook_field_views_data_views_data_alter()
 */
524
function hook_field_views_data(\Drupal\field\FieldStorageConfigInterface $field_storage) {
525
  $data = views_field_default_views_data($field_storage);
526 527
  foreach ($data as $table_name => $table_data) {
    // Add the relationship only on the target_id field.
528
    $data[$table_name][$field_storage->getName() . '_target_id']['relationship'] = [
529 530 531
      'id' => 'standard',
      'base' => 'file_managed',
      'base field' => 'target_id',
532 533
      'label' => t('image from @field_name', ['@field_name' => $field_storage->getName()]),
    ];
534 535 536 537 538 539 540 541 542 543
  }

  return $data;
}

/**
 * Alter the Views data for a single Field API field.
 *
 * This is called on all modules even if there is no hook_field_views_data()
 * implementation for the field, and therefore may be used to alter the
544
 * default data that views_field_default_views_data() supplies for the
545
 * field storage.
546
 *
547 548 549 550 551
 * @param array $data
 *   The views data for the field storage. This has the same format as the
 *   return value of hook_views_data().
 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
 *   The field storage config entity.
552
 *
553
 * @see views_views_data()
554 555 556
 * @see hook_field_views_data()
 * @see hook_field_views_data_views_data_alter()
 */
557
function hook_field_views_data_alter(array &$data, \Drupal\field\FieldStorageConfigInterface $field_storage) {
558
  $entity_type_id = $field_storage->getTargetEntityTypeId();
559
  $field_name = $field_storage->getName();
560 561
  $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
  $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type_id;
562
  $table_mapping = \Drupal::entityManager()->getStorage($entity_type_id)->getTableMapping();
563

564
  list($label) = views_entity_field_label($entity_type_id, $field_name);
565

566 567 568
  $data['file_managed'][$pseudo_field_name]['relationship'] = [
    'title' => t('@entity using @field', ['@entity' => $entity_type->getLabel(), '@field' => $label]),
    'help' => t('Relate each @entity with a @field set to the image.', ['@entity' => $entity_type->getLabel(), '@field' => $label]),
569 570 571
    'id' => 'entity_reverse',
    'field_name' => $field_name,
    'entity_type' => $entity_type_id,
572
    'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
573 574 575
    'field field' => $field_name . '_target_id',
    'base' => $entity_type->getBaseTable(),
    'base field' => $entity_type->getKey('id'),
576
    'label' => $field_name,
577 578
    'join_extra' => [
      0 => [
579 580 581
        'field' => 'deleted',
        'value' => 0,
        'numeric' => TRUE,
582 583 584
      ],
    ],
  ];
585 586 587 588 589 590
}

/**
 * Alter the Views data on a per field basis.
 *
 * The field module's implementation of hook_views_data_alter() invokes this for
591 592
 * each field storage, in the module that defines the field type. It is not
 * invoked in other modules.
593 594
 *
 * Unlike hook_field_views_data_alter(), this operates on the whole of the views
595
 * data. This allows a field type to add data that concerns its fields in
596 597 598 599 600 601 602 603 604 605
 * other tables, which would not yet be defined at the point when
 * hook_field_views_data() and hook_field_views_data_alter() are invoked. For
 * example, entityreference adds reverse relationships on the tables for the
 * entities which are referenced by entityreference fields.
 *
 * (Note: this is weirdly named so as not to conflict with
 * hook_field_views_data_alter().)
 *
 * @param array $data
 *   The views data.
606 607
 * @param \Drupal\field\FieldStorageConfigInterface $field
 *   The field storage config entity.
608 609 610
 *
 * @see hook_field_views_data()
 * @see hook_field_views_data_alter()
611
 * @see views_views_data_alter()
612
 */
613
function hook_field_views_data_views_data_alter(array &$data, \Drupal\field\FieldStorageConfigInterface $field) {
614 615 616 617 618
  $field_name = $field->getName();
  $data_key = 'field_data_' . $field_name;
  $entity_type_id = $field->entity_type;
  $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
  $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type_id;
619
  list($label) = views_entity_field_label($entity_type_id, $field_name);
620
  $table_mapping = \Drupal::entityManager()->getStorage($entity_type_id)->getTableMapping();
621 622

  // Views data for this field is in $data[$data_key].
623 624 625
  $data[$data_key][$pseudo_field_name]['relationship'] = [
    'title' => t('@entity using @field', ['@entity' => $entity_type->getLabel(), '@field' => $label]),
    'help' => t('Relate each @entity with a @field set to the term.', ['@entity' => $entity_type->getLabel(), '@field' => $label]),
626 627 628
    'id' => 'entity_reverse',
    'field_name' => $field_name,
    'entity_type' => $entity_type_id,
629
    'field table' => $table_mapping->getDedicatedDataTableName($field),
630 631 632
    'field field' => $field_name . '_target_id',
    'base' => $entity_type->getBaseTable(),
    'base field' => $entity_type->getKey('id'),
633
    'label' => $field_name,
634 635
    'join_extra' => [
      0 => [
636 637 638
        'field' => 'deleted',
        'value' => 0,
        'numeric' => TRUE,
639 640 641
      ],
    ],
  ];
642 643
}

merlinofchaos's avatar
merlinofchaos committed
644
/**
645
 * Replace special strings in the query before it is executed.
merlinofchaos's avatar
merlinofchaos committed
646
 *
647 648 649 650
 * The idea is that certain dynamic values can be placed in a query when it is
 * built, and substituted at run-time, allowing the query to be cached and
 * still work correctly when executed.
 *
651
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
652
 *   The View being executed.
653
 *
654 655 656
 * @return array
 *   An associative array where each key is a string to be replaced, and the
 *   corresponding value is its replacement. The strings to replace are often
657 658
 *   surrounded with '***', as illustrated in the example implementation, to
 *   avoid collisions with other values in the query.
merlinofchaos's avatar
merlinofchaos committed
659
 */
660
function hook_views_query_substitutions(ViewExecutable $view) {
merlinofchaos's avatar
merlinofchaos committed
661
  // Example from views_views_query_substitutions().
662
  return [
663
    '***CURRENT_VERSION***' => \Drupal::VERSION,
merlinofchaos's avatar
merlinofchaos committed
664
    '***CURRENT_TIME***' => REQUEST_TIME,
665 666
    '***LANGUAGE_language_content***' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(),
    PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT => \Drupal::languageManager()->getDefaultLanguage()->getId(),
667
  ];
merlinofchaos's avatar
merlinofchaos committed
668 669 670
}

/**
671
 * Replace special strings when processing a view with form elements.
merlinofchaos's avatar
merlinofchaos committed
672
 *
673 674
 * @return array
 *   An associative array where each key is a string to be replaced, and the
675 676
 *   corresponding value is its replacement. The value will be escaped unless it
 *   is already marked safe.
merlinofchaos's avatar
merlinofchaos committed
677 678
 */
function hook_views_form_substitutions() {
679
  return [
merlinofchaos's avatar
merlinofchaos committed
680
    '<!--views-form-example-substitutions-->' => 'Example Substitution',
681
  ];
merlinofchaos's avatar
merlinofchaos committed
682 683 684
}

/**
685
 * Alter a view at the very beginning of Views processing.
merlinofchaos's avatar
merlinofchaos committed
686
 *
687 688 689
 * Output can be added to the view by setting $view->attachment_before
 * and $view->attachment_after.
 *
690
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
691
 *   The view object about to be processed.
692
 * @param string $display_id
merlinofchaos's avatar
merlinofchaos committed
693
 *   The machine name of the active display.
694
 * @param array $args
merlinofchaos's avatar
merlinofchaos committed
695
 *   An array of arguments passed into the view.
696
 *
697
 * @see \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
698
 */
699 700 701
function hook_views_pre_view(ViewExecutable $view, $display_id, array &$args) {

  // Modify contextual filters for my_special_view if user has 'my special permission'.
702 703
  $account = \Drupal::currentUser();

704
  if ($view->id() == 'my_special_view' && $account->hasPermission('my special permission') && $display_id == 'public_display') {
705
    $args[0] = 'custom value';
merlinofchaos's avatar
merlinofchaos committed
706 707 708 709
  }
}

/**
710
 * Act on the view before the query is built, but after displays are attached.
merlinofchaos's avatar
merlinofchaos committed
711
 *
712 713 714
 * Output can be added to the view by setting $view->attachment_before
 * and $view->attachment_after.
 *
715
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
716
 *   The view object about to be processed.
717
 *
718
 * @see \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
719
 */
720
function hook_views_pre_build(ViewExecutable $view) {
721
  // Because of some inexplicable business logic, we should remove all
merlinofchaos's avatar
merlinofchaos committed
722 723 724 725 726 727 728 729 730
  // attachments from all views on Mondays.
  // (This alter could be done later in the execution process as well.)
  if (date('D') == 'Mon') {
    unset($view->attachment_before);
    unset($view->attachment_after);
  }
}

/**
731
 * Act on the view immediately after the query is built.
merlinofchaos's avatar
merlinofchaos committed
732
 *
733 734 735
 * Output can be added to the view by setting $view->attachment_before
 * and $view->attachment_after.
 *
736
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
737
 *   The view object about to be processed.
738
 *
739
 * @see \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
740
 */
741
function hook_views_post_build(ViewExecutable $view) {
merlinofchaos's avatar
merlinofchaos committed
742 743 744 745 746
  // If the exposed field 'type' is set, hide the column containing the content
  // type. (Note that this is a solution for a particular view, and makes
  // assumptions about both exposed filter settings and the fields in the view.
  // Also note that this alter could be done at any point before the view being
  // rendered.)
747
  if ($view->id() == 'my_view' && isset($view->exposed_raw_input['type']) && $view->exposed_raw_input['type'] != 'All') {
merlinofchaos's avatar
merlinofchaos committed
748 749 750 751 752 753 754 755
    // 'Type' should be interpreted as content type.
    if (isset($view->field['type'])) {
      $view->field['type']->options['exclude'] = TRUE;
    }
  }
}

/**
756
 * Act on the view after the query is built and just before it is executed.
merlinofchaos's avatar
merlinofchaos committed
757
 *
758 759 760
 * Output can be added to the view by setting $view->attachment_before
 * and $view->attachment_after.
 *
761
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
762
 *   The view object about to be processed.
763
 *
764
 * @see \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
765
 */
766
function hook_views_pre_execute(ViewExecutable $view) {
merlinofchaos's avatar
merlinofchaos committed
767 768 769 770
  // Whenever a view queries more than two tables, show a message that notifies
  // view administrators that the query might be heavy.
  // (This action could be performed later in the execution process, but not
  // earlier.)
771 772 773
  $account = \Drupal::currentUser();

  if (count($view->query->tables) > 2 && $account->hasPermission('administer views')) {
774
    \Drupal::messenger()->addWarning(t('The view %view may be heavy to execute.', ['%view' => $view->id()]));
merlinofchaos's avatar
merlinofchaos committed
775 776 777 778
  }
}

/**
779
 * Act on the view immediately after the query has been executed.
merlinofchaos's avatar
merlinofchaos committed
780
 *
781
 * At this point the query has been executed, but the preRender() phase has
782 783 784 785 786
 * not yet happened for handlers.
 *
 * Output can be added to the view by setting $view->attachment_before
 * and $view->attachment_after.
 *
787
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
788
 *   The view object about to be processed.
789
 *
790
 * @see \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
791
 */
792
function hook_views_post_execute(ViewExecutable $view) {
merlinofchaos's avatar
merlinofchaos committed
793 794 795 796 797
  // If there are more than 100 results, show a message that encourages the user
  // to change the filter settings.
  // (This action could be performed later in the execution process, but not
  // earlier.)
  if ($view->total_rows > 100) {
798
    \Drupal::messenger()->addStatus(t('You have more than 100 hits. Use the filter settings to narrow down your list.'));
merlinofchaos's avatar
merlinofchaos committed
799 800 801 802
  }
}

/**
803
 * Act on the view immediately before rendering it.
merlinofchaos's avatar
merlinofchaos committed
804
 *
805
 * At this point the query has been executed, and the preRender() phase has
806
 * already happened for handlers, so all data should be available. This hook
807
 * can be used by themes.
merlinofchaos's avatar
merlinofchaos committed
808
 *
809 810 811
 * Output can be added to the view by setting $view->attachment_before
 * and $view->attachment_after.
 *
812
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
813
 *   The view object about to be processed.
814
 *
815
 * @see \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
816
 */
817
function hook_views_pre_render(ViewExecutable $view) {
merlinofchaos's avatar
merlinofchaos committed
818 819 820 821 822 823 824
  // Scramble the order of the rows shown on this result page.
  // Note that this could be done earlier, but not later in the view execution
  // process.
  shuffle($view->result);
}

/**
825
 * Post-process any rendered data.
merlinofchaos's avatar
merlinofchaos committed
826 827 828
 *
 * This can be valuable to be able to cache a view and still have some level of
 * dynamic output. In an ideal world, the actual output will include HTML
829
 * comment-based tokens, and then the post process can replace those tokens.
830
 * This hook can be used by themes.
merlinofchaos's avatar
merlinofchaos committed
831 832 833
 *
 * Example usage. If it is known that the view is a node view and that the
 * primary field will be a nid, you can do something like this:
834 835
 * @code
 *   <!--post-FIELD-NID-->
836
 * @endcode
837
 * And then in the post-render, create an array with the text that should
merlinofchaos's avatar
merlinofchaos committed
838
 * go there:
839 840
 * @code
 *   strtr($output, array('<!--post-FIELD-1-->' => 'output for FIELD of nid 1');
841
 * @endcode
merlinofchaos's avatar
merlinofchaos committed
842 843 844
 * All of the cached result data will be available in $view->result, as well,
 * so all ids used in the query should be discoverable.
 *
845
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
846
 *   The view object about to be processed.
847
 * @param string $output
merlinofchaos's avatar
merlinofchaos committed
848
 *   A flat string with the rendered output of the view.
849
 * @param \Drupal\views\Plugin\views\cache\CachePluginBase $cache
merlinofchaos's avatar
merlinofchaos committed
850
 *   The cache settings.
851
 *
852
 * @see \Drupal\views\ViewExecutable
merlinofchaos's avatar
merlinofchaos committed
853
 */
854
function hook_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
855 856 857 858 859
  // When using full pager, disable any time-based caching if there are fewer
  // than 10 results.
  if ($view->pager instanceof Drupal\views\Plugin\views\pager\Full && $cache instanceof Drupal\views\Plugin\views\cache\Time && count($view->result) < 10) {
    $cache->options['results_lifespan'] = 0;
    $cache->options['output_lifespan'] = 0;
merlinofchaos's avatar
merlinofchaos committed
860 861 862 863
  }
}

/**
864
 * Alter the query before it is executed.
merlinofchaos's avatar
merlinofchaos committed
865
 *
866
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
867
 *   The view object about to be processed.
868 869 870
 * @param QueryPluginBase $query
 *   The query plugin object for the query.
 *
merlinofchaos's avatar
merlinofchaos committed
871
 * @see hook_views_query_substitutions()
872
 * @see \Drupal\views\Plugin\views\query\Sql
merlinofchaos's avatar
merlinofchaos committed
873
 */
874
function hook_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
merlinofchaos's avatar
merlinofchaos committed
875 876 877
  // (Example assuming a view with an exposed filter on node title.)
  // If the input for the title filter is a positive integer, filter against
  // node ID instead of node title.
878
  if ($view->id() == 'my_view' && is_numeric($view->exposed_raw_input['title']) && $view->exposed_raw_input['title'] > 0) {
merlinofchaos's avatar
merlinofchaos committed
879 880 881 882 883 884
    // Traverse through the 'where' part of the query.
    foreach ($query->where as &$condition_group) {
      foreach ($condition_group['conditions'] as &$condition) {
        // If this is the part of the query filtering on title, chang the
        // condition to filter on node ID.
        if ($condition['field'] == 'node.title') {
885
          $condition = [
merlinofchaos's avatar
merlinofchaos committed
886 887 888
            'field' => 'node.nid',
            'value' => $view->exposed_raw_input['title'],
            'operator' => '=',
889
          ];
merlinofchaos's avatar
merlinofchaos committed
890 891 892 893 894 895 896
        }
      }
    }
  }
}

/**
897 898 899 900 901
 * Alter the view preview information.
 *
 * The view preview information is optionally displayed when a view is
 * previewed in the administrative UI. It includes query and performance
 * statistics.
merlinofchaos's avatar
merlinofchaos committed
902
 *
903
 * @param array $rows
merlinofchaos's avatar
merlinofchaos committed
904
 *   An associative array with two keys:
905
 *   - query: An array of rows suitable for '#type' => 'table', containing
merlinofchaos's avatar
merlinofchaos committed
906
 *     information about the query and the display title and path.
907
 *   - statistics: An array of rows suitable for '#type' => 'table',
908
 *     containing performance statistics.
909
 * @param \Drupal\views\ViewExecutable $view
merlinofchaos's avatar
merlinofchaos committed
910
 *   The view object.
911
 *
912
 * @see \Drupal\views_ui\ViewUI
913
 * @see table.html.twig
merlinofchaos's avatar
merlinofchaos committed
914
 */
915
function hook_views_preview_info_alter(array &$rows, ViewExecutable $view) {
merlinofchaos's avatar
merlinofchaos committed
916 917
  // Adds information about the tables being queried by the view to the query
  // part of the info box.
918
  $rows['query'][] = [
merlinofchaos's avatar
merlinofchaos committed
919 920
    t('<strong>Table queue</strong>'),
    count($view->query->table_queue) . ': (' . implode(', ', array_keys($view->query->table_queue)) . ')',
921
  ];
merlinofchaos's avatar
merlinofchaos committed
922 923 924
}

/**
925 926 927 928
 * Alter the links displayed at the top of the view edit form.
 *
 * @param array $links
 *   A renderable array of links which will be displayed at the top of the
929
 *   view edit form. Each entry will be in a form suitable for
930
 *   '#theme' => 'links'.
931 932 933 934 935 936
 * @param \Drupal\views\ViewExecutable $view
 *   The view object being edited.
 * @param string $display_id
 *   The ID of the display being edited, e.g. 'default' or 'page_1'.
 *
 * @see \Drupal\views_ui\ViewUI::renderDisplayTop()
merlinofchaos's avatar
merlinofchaos committed
937
 */
938
function hook_views_ui_display_top_links_alter(array &$links, ViewExecutable $view, $display_id) {
merlinofchaos's avatar
merlinofchaos committed
939 940
  // Put the export link first in the list.
  if (isset($links['export'])) {
941
    $links = ['export' => $links['export']] + $links;
merlinofchaos's avatar
merlinofchaos committed
942 943 944
  }
}

945
// @todo Describe how to alter a view ajax response with event listeners.
merlinofchaos's avatar
merlinofchaos committed
946

947
/**
948
 * Allow modules to respond to the invalidation of the Views cache.
949
 *
950
 * This hook will fire whenever a view is enabled, disabled, created,
951 952 953 954 955
 * updated, or deleted.
 *
 * @see views_invalidate_cache()
 */
function hook_views_invalidate_cache() {
956
  \Drupal\Core\Cache\Cache::invalidateTags(['views']);
957 958
}

959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
/**
 * Modify the list of available views access plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_access_alter(array &$plugins) {
  // Remove the available plugin because the users should not have access to it.
  unset($plugins['role']);
}

/**
 * Modify the list of available views default argument plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_argument_default_alter(array &$plugins) {
  // Remove the available plugin because the users should not have access to it.
  unset($plugins['php']);
}

/**
 * Modify the list of available views argument validation plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_argument_validator_alter(array &$plugins) {
  // Remove the available plugin because the users should not have access to it.
  unset($plugins['php']);
}

/**
 * Modify the list of available views cache plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_cache_alter(array &$plugins) {
  // Change the title.
  $plugins['time']['title'] = t('Custom title');
}

/**
 * Modify the list of available views display extender plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_display_extenders_alter(array &$plugins) {
  // Alter the title of an existing plugin.
  $plugins['time']['title'] = t('Custom title');
}

/**
 * Modify the list of available views display plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_display_alter(array &$plugins) {
  // Alter the title of an existing plugin.
  $plugins['rest_export']['title'] = t('Export');
}

/**
 * Modify the list of available views exposed form plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_exposed_form_alter(array &$plugins) {
  // Remove the available plugin because the users should not have access to it.
  unset($plugins['input_required']);
}

/**
 * Modify the list of available views join plugins.
 *
 * This hook may be used to modify plugin properties after they have been
 * specified by other modules.
 *
 * @param array $plugins
 *   An array of all the existing plugin definitions, passed by reference.
 *
 * @see \Drupal\views\Plugin\ViewsPluginManager
 */
function hook_views_plugins_join_alter(array &$plugins) {
  // Print out all join plugin names for debugging purposes.
  debug($plugins);
}

/**
1088
 * Modify the list of available views pager plugins.
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152