annotate core/modules/views/views.views.inc @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /**
Chris@0 4 * @file
Chris@0 5 * Provide views data that isn't tied to any other module.
Chris@0 6 */
Chris@0 7
Chris@0 8 use Drupal\Component\Utility\NestedArray;
Chris@0 9 use Drupal\Core\Entity\EntityStorageInterface;
Chris@0 10 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
Chris@0 11 use Drupal\Core\Render\Markup;
Chris@0 12 use Drupal\field\Entity\FieldConfig;
Chris@0 13 use Drupal\field\FieldConfigInterface;
Chris@0 14 use Drupal\field\FieldStorageConfigInterface;
Chris@0 15 use Drupal\system\ActionConfigEntityInterface;
Chris@0 16
Chris@0 17 /**
Chris@0 18 * Implements hook_views_data().
Chris@0 19 */
Chris@0 20 function views_views_data() {
Chris@0 21 $data['views']['table']['group'] = t('Global');
Chris@0 22 $data['views']['table']['join'] = [
Chris@0 23 // #global is a special flag which allows a table to appear all the time.
Chris@0 24 '#global' => [],
Chris@0 25 ];
Chris@0 26
Chris@0 27 $data['views']['random'] = [
Chris@0 28 'title' => t('Random'),
Chris@0 29 'help' => t('Randomize the display order.'),
Chris@0 30 'sort' => [
Chris@0 31 'id' => 'random',
Chris@0 32 ],
Chris@0 33 ];
Chris@0 34
Chris@0 35 $data['views']['null'] = [
Chris@0 36 'title' => t('Null'),
Chris@0 37 'help' => t('Allow a contextual filter value to be ignored. The query will not be altered by this contextual filter value. Can be used when contextual filter values come from the URL, and a part of the URL needs to be ignored.'),
Chris@0 38 'argument' => [
Chris@0 39 'id' => 'null',
Chris@0 40 ],
Chris@0 41 ];
Chris@0 42
Chris@0 43 $data['views']['nothing'] = [
Chris@0 44 'title' => t('Custom text'),
Chris@0 45 'help' => t('Provide custom text or link.'),
Chris@0 46 'field' => [
Chris@0 47 'id' => 'custom',
Chris@0 48 ],
Chris@0 49 ];
Chris@0 50
Chris@0 51 $data['views']['counter'] = [
Chris@0 52 'title' => t('View result counter'),
Chris@0 53 'help' => t('Displays the actual position of the view result'),
Chris@0 54 'field' => [
Chris@0 55 'id' => 'counter',
Chris@0 56 ],
Chris@0 57 ];
Chris@0 58
Chris@0 59 $data['views']['area'] = [
Chris@0 60 'title' => t('Text area'),
Chris@0 61 'help' => t('Provide markup text for the area.'),
Chris@0 62 'area' => [
Chris@0 63 'id' => 'text',
Chris@0 64 ],
Chris@0 65 ];
Chris@0 66
Chris@0 67 $data['views']['area_text_custom'] = [
Chris@0 68 'title' => t('Unfiltered text'),
Chris@0 69 'help' => t('Add unrestricted, custom text or markup. This is similar to the custom text field.'),
Chris@0 70 'area' => [
Chris@0 71 'id' => 'text_custom',
Chris@0 72 ],
Chris@0 73 ];
Chris@0 74
Chris@0 75 $data['views']['title'] = [
Chris@0 76 'title' => t('Title override'),
Chris@0 77 'help' => t('Override the default view title for this view. This is useful to display an alternative title when a view is empty.'),
Chris@0 78 'area' => [
Chris@0 79 'id' => 'title',
Chris@0 80 'sub_type' => 'empty',
Chris@0 81 ],
Chris@0 82 ];
Chris@0 83
Chris@0 84 $data['views']['view'] = [
Chris@0 85 'title' => t('View area'),
Chris@0 86 'help' => t('Insert a view inside an area.'),
Chris@0 87 'area' => [
Chris@0 88 'id' => 'view',
Chris@0 89 ],
Chris@0 90 ];
Chris@0 91
Chris@0 92 $data['views']['result'] = [
Chris@0 93 'title' => t('Result summary'),
Chris@0 94 'help' => t('Shows result summary, for example the items per page.'),
Chris@0 95 'area' => [
Chris@0 96 'id' => 'result',
Chris@0 97 ],
Chris@0 98 ];
Chris@0 99
Chris@0 100 $data['views']['messages'] = [
Chris@0 101 'title' => t('Messages'),
Chris@0 102 'help' => t('Displays messages in an area.'),
Chris@0 103 'area' => [
Chris@0 104 'id' => 'messages',
Chris@0 105 ],
Chris@0 106 ];
Chris@0 107
Chris@0 108 $data['views']['http_status_code'] = [
Chris@0 109 'title' => t('Response status code'),
Chris@0 110 'help' => t('Alter the HTTP response status code used by this view, mostly helpful for empty results.'),
Chris@0 111 'area' => [
Chris@0 112 'id' => 'http_status_code',
Chris@0 113 ],
Chris@0 114 ];
Chris@0 115
Chris@0 116 $data['views']['combine'] = [
Chris@0 117 'title' => t('Combine fields filter'),
Chris@0 118 'help' => t('Combine multiple fields together and search by them.'),
Chris@0 119 'filter' => [
Chris@0 120 'id' => 'combine',
Chris@0 121 ],
Chris@0 122 ];
Chris@0 123
Chris@0 124 $data['views']['dropbutton'] = [
Chris@0 125 'title' => t('Dropbutton'),
Chris@0 126 'help' => t('Display fields in a dropbutton.'),
Chris@0 127 'field' => [
Chris@0 128 'id' => 'dropbutton',
Chris@0 129 ],
Chris@0 130 ];
Chris@0 131
Chris@18 132 $data['views']['display_link'] = [
Chris@18 133 'title' => t('Link to display'),
Chris@18 134 'help' => t('Displays a link to a path-based display of this view while keeping the filter criteria, sort criteria, pager settings and contextual filters.'),
Chris@18 135 'area' => [
Chris@18 136 'id' => 'display_link',
Chris@18 137 ],
Chris@18 138 ];
Chris@18 139
Chris@0 140 // Registers an entity area handler per entity type.
Chris@0 141 foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) {
Chris@0 142 // Excludes entity types, which cannot be rendered.
Chris@0 143 if ($entity_type->hasViewBuilderClass()) {
Chris@0 144 $label = $entity_type->getLabel();
Chris@0 145 $data['views']['entity_' . $entity_type_id] = [
Chris@0 146 'title' => t('Rendered entity - @label', ['@label' => $label]),
Chris@0 147 'help' => t('Displays a rendered @label entity in an area.', ['@label' => $label]),
Chris@0 148 'area' => [
Chris@0 149 'entity_type' => $entity_type_id,
Chris@0 150 'id' => 'entity',
Chris@0 151 ],
Chris@0 152 ];
Chris@0 153 }
Chris@0 154 }
Chris@0 155
Chris@0 156 // Registers an action bulk form per entity.
Chris@0 157 foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) {
Chris@0 158 $actions = array_filter(\Drupal::entityManager()->getStorage('action')->loadMultiple(), function (ActionConfigEntityInterface $action) use ($entity_type) {
Chris@0 159 return $action->getType() == $entity_type;
Chris@0 160 });
Chris@0 161 if (empty($actions)) {
Chris@0 162 continue;
Chris@0 163 }
Chris@0 164 $data[$entity_info->getBaseTable()][$entity_type . '_bulk_form'] = [
Chris@0 165 'title' => t('Bulk update'),
Chris@0 166 'help' => t('Allows users to apply an action to one or more items.'),
Chris@0 167 'field' => [
Chris@0 168 'id' => 'bulk_form',
Chris@0 169 ],
Chris@0 170 ];
Chris@0 171 }
Chris@0 172
Chris@0 173 // Registers views data for the entity itself.
Chris@0 174 foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) {
Chris@0 175 if ($entity_type->hasHandlerClass('views_data')) {
Chris@0 176 /** @var \Drupal\views\EntityViewsDataInterface $views_data */
Chris@0 177 $views_data = \Drupal::entityManager()->getHandler($entity_type_id, 'views_data');
Chris@0 178 $data = NestedArray::mergeDeep($data, $views_data->getViewsData());
Chris@0 179 }
Chris@0 180 }
Chris@0 181
Chris@0 182 // Field modules can implement hook_field_views_data() to override the default
Chris@0 183 // behavior for adding fields.
Chris@0 184 $module_handler = \Drupal::moduleHandler();
Chris@0 185
Chris@0 186 $entity_manager = \Drupal::entityManager();
Chris@0 187 if ($entity_manager->hasDefinition('field_storage_config')) {
Chris@0 188 /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
Chris@0 189 foreach ($entity_manager->getStorage('field_storage_config')->loadMultiple() as $field_storage) {
Chris@0 190 if (_views_field_get_entity_type_storage($field_storage)) {
Chris@0 191 $result = (array) $module_handler->invoke($field_storage->getTypeProvider(), 'field_views_data', [$field_storage]);
Chris@0 192 if (empty($result)) {
Chris@0 193 $result = views_field_default_views_data($field_storage);
Chris@0 194 }
Chris@0 195 $module_handler->alter('field_views_data', $result, $field_storage);
Chris@0 196
Chris@0 197 if (is_array($result)) {
Chris@0 198 $data = NestedArray::mergeDeep($result, $data);
Chris@0 199 }
Chris@0 200 }
Chris@0 201 }
Chris@0 202 }
Chris@0 203
Chris@0 204 return $data;
Chris@0 205 }
Chris@0 206
Chris@0 207 /**
Chris@0 208 * Implements hook_views_data_alter().
Chris@0 209 *
Chris@0 210 * Field modules can implement hook_field_views_data_views_data_alter() to
Chris@0 211 * alter the views data on a per field basis. This is weirdly named so as
Chris@0 212 * not to conflict with the \Drupal::moduleHandler()->alter('field_views_data')
Chris@0 213 * in views_views_data().
Chris@0 214 */
Chris@0 215 function views_views_data_alter(&$data) {
Chris@0 216 $entity_manager = \Drupal::entityManager();
Chris@0 217 if (!$entity_manager->hasDefinition('field_storage_config')) {
Chris@0 218 return;
Chris@0 219 }
Chris@0 220 /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
Chris@0 221 foreach ($entity_manager->getStorage('field_storage_config')->loadMultiple() as $field_storage) {
Chris@0 222 if (_views_field_get_entity_type_storage($field_storage)) {
Chris@0 223 $function = $field_storage->getTypeProvider() . '_field_views_data_views_data_alter';
Chris@0 224 if (function_exists($function)) {
Chris@0 225 $function($data, $field_storage);
Chris@0 226 }
Chris@0 227 }
Chris@0 228 }
Chris@0 229 }
Chris@0 230
Chris@0 231 /**
Chris@0 232 * Determines whether the entity type the field appears in is SQL based.
Chris@0 233 *
Chris@0 234 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
Chris@0 235 * The field storage definition.
Chris@0 236 *
Chris@0 237 * @return \Drupal\Core\Entity\Sql\SqlContentEntityStorage
Chris@0 238 * Returns the entity type storage if supported.
Chris@0 239 */
Chris@0 240 function _views_field_get_entity_type_storage(FieldStorageConfigInterface $field_storage) {
Chris@0 241 $result = FALSE;
Chris@0 242 $entity_manager = \Drupal::entityManager();
Chris@0 243 if ($entity_manager->hasDefinition($field_storage->getTargetEntityTypeId())) {
Chris@0 244 $storage = $entity_manager->getStorage($field_storage->getTargetEntityTypeId());
Chris@0 245 $result = $storage instanceof SqlContentEntityStorage ? $storage : FALSE;
Chris@0 246 }
Chris@0 247 return $result;
Chris@0 248 }
Chris@0 249
Chris@0 250 /**
Chris@0 251 * Returns the label of a certain field.
Chris@0 252 *
Chris@0 253 * Therefore it looks up in all bundles to find the most used field.
Chris@0 254 */
Chris@0 255 function views_entity_field_label($entity_type, $field_name) {
Chris@0 256 $label_counter = [];
Chris@0 257 $all_labels = [];
Chris@0 258 // Count the amount of fields per label per field storage.
Chris@18 259 foreach (array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type)) as $bundle) {
Chris@0 260 $bundle_fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle), function ($field_definition) {
Chris@0 261 return $field_definition instanceof FieldConfigInterface;
Chris@0 262 });
Chris@0 263 if (isset($bundle_fields[$field_name])) {
Chris@0 264 $field = $bundle_fields[$field_name];
Chris@0 265 $label = $field->getLabel();
Chris@0 266 $label_counter[$label] = isset($label_counter[$label]) ? ++$label_counter[$label] : 1;
Chris@0 267 $all_labels[$label] = TRUE;
Chris@0 268 }
Chris@0 269 }
Chris@0 270 if (empty($label_counter)) {
Chris@0 271 return [$field_name, $all_labels];
Chris@0 272 }
Chris@0 273 // Sort the field labels by it most used label and return the most used one.
Chris@0 274 // If the counts are equal, sort by the label to ensure the result is
Chris@0 275 // deterministic.
Chris@0 276 uksort($label_counter, function ($a, $b) use ($label_counter) {
Chris@0 277 if ($label_counter[$a] === $label_counter[$b]) {
Chris@0 278 return strcmp($a, $b);
Chris@0 279 }
Chris@0 280 return $label_counter[$a] > $label_counter[$b] ? -1 : 1;
Chris@0 281 });
Chris@0 282 $label_counter = array_keys($label_counter);
Chris@0 283 return [$label_counter[0], $all_labels];
Chris@0 284 }
Chris@0 285
Chris@0 286 /**
Chris@0 287 * Default views data implementation for a field.
Chris@0 288 *
Chris@0 289 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
Chris@0 290 * The field definition.
Chris@0 291 *
Chris@0 292 * @return array
Chris@0 293 * The default views data for the field.
Chris@0 294 */
Chris@0 295 function views_field_default_views_data(FieldStorageConfigInterface $field_storage) {
Chris@0 296 $data = [];
Chris@0 297
Chris@0 298 // Check the field type is available.
Chris@0 299 if (!\Drupal::service('plugin.manager.field.field_type')->hasDefinition($field_storage->getType())) {
Chris@0 300 return $data;
Chris@0 301 }
Chris@0 302 // Check the field storage has fields.
Chris@0 303 if (!$field_storage->getBundles()) {
Chris@0 304 return $data;
Chris@0 305 }
Chris@0 306
Chris@0 307 // Ignore custom storage too.
Chris@0 308 if ($field_storage->hasCustomStorage()) {
Chris@0 309 return $data;
Chris@0 310 }
Chris@0 311
Chris@0 312 // Check whether the entity type storage is supported.
Chris@0 313 $storage = _views_field_get_entity_type_storage($field_storage);
Chris@0 314 if (!$storage) {
Chris@0 315 return $data;
Chris@0 316 }
Chris@0 317
Chris@0 318 $field_name = $field_storage->getName();
Chris@0 319 $field_columns = $field_storage->getColumns();
Chris@0 320
Chris@0 321 // Grab information about the entity type tables.
Chris@0 322 // We need to join to both the base table and the data table, if available.
Chris@0 323 $entity_manager = \Drupal::entityManager();
Chris@0 324 $entity_type_id = $field_storage->getTargetEntityTypeId();
Chris@0 325 $entity_type = $entity_manager->getDefinition($entity_type_id);
Chris@0 326 if (!$base_table = $entity_type->getBaseTable()) {
Chris@0 327 // We cannot do anything if for some reason there is no base table.
Chris@0 328 return $data;
Chris@0 329 }
Chris@0 330 $entity_tables = [$base_table => $entity_type_id];
Chris@0 331 // Some entities may not have a data table.
Chris@0 332 $data_table = $entity_type->getDataTable();
Chris@0 333 if ($data_table) {
Chris@0 334 $entity_tables[$data_table] = $entity_type_id;
Chris@0 335 }
Chris@0 336 $entity_revision_table = $entity_type->getRevisionTable();
Chris@0 337 $supports_revisions = $entity_type->hasKey('revision') && $entity_revision_table;
Chris@0 338 if ($supports_revisions) {
Chris@0 339 $entity_tables[$entity_revision_table] = $entity_type_id;
Chris@0 340 $entity_revision_data_table = $entity_type->getRevisionDataTable();
Chris@0 341 if ($entity_revision_data_table) {
Chris@0 342 $entity_tables[$entity_revision_data_table] = $entity_type_id;
Chris@0 343 }
Chris@0 344 }
Chris@0 345
Chris@0 346 // Description of the field tables.
Chris@0 347 // @todo Generalize this code to make it work with any table layout. See
Chris@0 348 // https://www.drupal.org/node/2079019.
Chris@0 349 $table_mapping = $storage->getTableMapping();
Chris@0 350 $field_tables = [
Chris@0 351 EntityStorageInterface::FIELD_LOAD_CURRENT => [
Chris@0 352 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
Chris@0 353 'alias' => "{$entity_type_id}__{$field_name}",
Chris@0 354 ],
Chris@0 355 ];
Chris@0 356 if ($supports_revisions) {
Chris@0 357 $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION] = [
Chris@0 358 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
Chris@0 359 'alias' => "{$entity_type_id}_revision__{$field_name}",
Chris@0 360 ];
Chris@0 361 }
Chris@0 362
Chris@0 363 // Determine if the fields are translatable.
Chris@0 364 $bundles_names = $field_storage->getBundles();
Chris@0 365 $translation_join_type = FALSE;
Chris@0 366 $fields = [];
Chris@0 367 $translatable_configs = [];
Chris@0 368 $untranslatable_configs = [];
Chris@0 369 $untranslatable_config_bundles = [];
Chris@0 370
Chris@0 371 foreach ($bundles_names as $bundle) {
Chris@0 372 $fields[$bundle] = FieldConfig::loadByName($entity_type->id(), $bundle, $field_name);
Chris@0 373 }
Chris@0 374 foreach ($fields as $bundle => $config_entity) {
Chris@0 375 if (!empty($config_entity)) {
Chris@0 376 if ($config_entity->isTranslatable()) {
Chris@0 377 $translatable_configs[$bundle] = $config_entity;
Chris@0 378 }
Chris@0 379 else {
Chris@0 380 $untranslatable_configs[$bundle] = $config_entity;
Chris@0 381 }
Chris@0 382 }
Chris@0 383 else {
Chris@0 384 // https://www.drupal.org/node/2451657#comment-11462881
Chris@0 385 \Drupal::logger('views')->error(
Chris@17 386 'A non-existent config entity name returned by FieldStorageConfigInterface::getBundles(): entity type: %entity_type, bundle: %bundle, field name: %field',
Chris@17 387 [
Chris@17 388 '%entity_type' => $entity_type->id(),
Chris@17 389 '%bundle' => $bundle,
Chris@17 390 '%field' => $field_name,
Chris@17 391 ]
Chris@17 392 );
Chris@0 393 }
Chris@0 394 }
Chris@0 395
Chris@0 396 // If the field is translatable on all the bundles, there will be a join on
Chris@0 397 // the langcode.
Chris@0 398 if (!empty($translatable_configs) && empty($untranslatable_configs)) {
Chris@0 399 $translation_join_type = 'language';
Chris@0 400 }
Chris@0 401 // If the field is translatable only on certain bundles, there will be a join
Chris@0 402 // on langcode OR bundle name.
Chris@0 403 elseif (!empty($translatable_configs) && !empty($untranslatable_configs)) {
Chris@0 404 foreach ($untranslatable_configs as $config) {
Chris@0 405 $untranslatable_config_bundles[] = $config->getTargetBundle();
Chris@0 406 }
Chris@0 407 $translation_join_type = 'language_bundle';
Chris@0 408 }
Chris@0 409
Chris@0 410 // Build the relationships between the field table and the entity tables.
Chris@0 411 $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias'];
Chris@0 412 if ($data_table) {
Chris@0 413 // Tell Views how to join to the base table, via the data table.
Chris@0 414 $data[$table_alias]['table']['join'][$data_table] = [
Chris@0 415 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
Chris@0 416 'left_field' => $entity_type->getKey('id'),
Chris@0 417 'field' => 'entity_id',
Chris@0 418 'extra' => [
Chris@0 419 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
Chris@0 420 ],
Chris@0 421 ];
Chris@0 422 }
Chris@0 423 else {
Chris@0 424 // If there is no data table, just join directly.
Chris@0 425 $data[$table_alias]['table']['join'][$base_table] = [
Chris@0 426 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
Chris@0 427 'left_field' => $entity_type->getKey('id'),
Chris@0 428 'field' => 'entity_id',
Chris@0 429 'extra' => [
Chris@0 430 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
Chris@0 431 ],
Chris@0 432 ];
Chris@0 433 }
Chris@0 434
Chris@0 435 if ($translation_join_type === 'language_bundle') {
Chris@0 436 $data[$table_alias]['table']['join'][$data_table]['join_id'] = 'field_or_language_join';
Chris@0 437 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
Chris@0 438 'left_field' => 'langcode',
Chris@0 439 'field' => 'langcode',
Chris@0 440 ];
Chris@0 441 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
Chris@0 442 'field' => 'bundle',
Chris@0 443 'value' => $untranslatable_config_bundles,
Chris@0 444 ];
Chris@0 445 }
Chris@0 446 elseif ($translation_join_type === 'language') {
Chris@0 447 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
Chris@0 448 'left_field' => 'langcode',
Chris@0 449 'field' => 'langcode',
Chris@0 450 ];
Chris@0 451 }
Chris@0 452
Chris@0 453 if ($supports_revisions) {
Chris@0 454 $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias'];
Chris@0 455 if ($entity_revision_data_table) {
Chris@0 456 // Tell Views how to join to the revision table, via the data table.
Chris@0 457 $data[$table_alias]['table']['join'][$entity_revision_data_table] = [
Chris@0 458 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
Chris@0 459 'left_field' => $entity_type->getKey('revision'),
Chris@0 460 'field' => 'revision_id',
Chris@0 461 'extra' => [
Chris@0 462 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
Chris@0 463 ],
Chris@0 464 ];
Chris@0 465 }
Chris@0 466 else {
Chris@0 467 // If there is no data table, just join directly.
Chris@0 468 $data[$table_alias]['table']['join'][$entity_revision_table] = [
Chris@0 469 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
Chris@0 470 'left_field' => $entity_type->getKey('revision'),
Chris@0 471 'field' => 'revision_id',
Chris@0 472 'extra' => [
Chris@0 473 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
Chris@0 474 ],
Chris@0 475 ];
Chris@0 476 }
Chris@0 477 if ($translation_join_type === 'language_bundle') {
Chris@0 478 $data[$table_alias]['table']['join'][$entity_revision_data_table]['join_id'] = 'field_or_language_join';
Chris@0 479 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
Chris@0 480 'left_field' => 'langcode',
Chris@0 481 'field' => 'langcode',
Chris@0 482 ];
Chris@0 483 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
Chris@0 484 'value' => $untranslatable_config_bundles,
Chris@0 485 'field' => 'bundle',
Chris@0 486 ];
Chris@0 487 }
Chris@0 488 elseif ($translation_join_type === 'language') {
Chris@0 489 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
Chris@0 490 'left_field' => 'langcode',
Chris@0 491 'field' => 'langcode',
Chris@0 492 ];
Chris@0 493 }
Chris@0 494 }
Chris@0 495
Chris@0 496 $group_name = $entity_type->getLabel();
Chris@0 497 // Get the list of bundles the field appears in.
Chris@0 498 $bundles_names = $field_storage->getBundles();
Chris@0 499 // Build the list of additional fields to add to queries.
Chris@0 500 $add_fields = ['delta', 'langcode', 'bundle'];
Chris@0 501 foreach (array_keys($field_columns) as $column) {
Chris@0 502 $add_fields[] = $table_mapping->getFieldColumnName($field_storage, $column);
Chris@0 503 }
Chris@0 504 // Determine the label to use for the field. We don't have a label available
Chris@0 505 // at the field level, so we just go through all fields and take the one
Chris@0 506 // which is used the most frequently.
Chris@0 507 list($label, $all_labels) = views_entity_field_label($entity_type_id, $field_name);
Chris@0 508
Chris@0 509 // Expose data for the field as a whole.
Chris@0 510 foreach ($field_tables as $type => $table_info) {
Chris@0 511 $table = $table_info['table'];
Chris@0 512 $table_alias = $table_info['alias'];
Chris@0 513
Chris@0 514 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
Chris@0 515 $group = $group_name;
Chris@0 516 $field_alias = $field_name;
Chris@0 517 }
Chris@0 518 else {
Chris@0 519 $group = t('@group (historical data)', ['@group' => $group_name]);
Chris@0 520 $field_alias = $field_name . '-revision_id';
Chris@0 521 }
Chris@0 522
Chris@0 523 $data[$table_alias][$field_alias] = [
Chris@0 524 'group' => $group,
Chris@0 525 'title' => $label,
Chris@0 526 'title short' => $label,
Chris@0 527 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
Chris@0 528 ];
Chris@0 529
Chris@0 530 // Go through and create a list of aliases for all possible combinations of
Chris@0 531 // entity type + name.
Chris@0 532 $aliases = [];
Chris@0 533 $also_known = [];
Chris@0 534 foreach ($all_labels as $label_name => $true) {
Chris@0 535 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
Chris@0 536 if ($label != $label_name) {
Chris@0 537 $aliases[] = [
Chris@0 538 'base' => $base_table,
Chris@0 539 'group' => $group_name,
Chris@0 540 'title' => $label_name,
Chris@0 541 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
Chris@0 542 ];
Chris@0 543 $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $label_name]);
Chris@0 544 }
Chris@0 545 }
Chris@0 546 elseif ($supports_revisions && $label != $label_name) {
Chris@0 547 $aliases[] = [
Chris@0 548 'base' => $table,
Chris@0 549 'group' => t('@group (historical data)', ['@group' => $group_name]),
Chris@0 550 'title' => $label_name,
Chris@0 551 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
Chris@0 552 ];
Chris@0 553 $also_known[] = t('@group (historical data): @field', ['@group' => $group_name, '@field' => $label_name]);
Chris@0 554 }
Chris@0 555 }
Chris@0 556 if ($aliases) {
Chris@0 557 $data[$table_alias][$field_alias]['aliases'] = $aliases;
Chris@0 558 // The $also_known variable contains markup that is HTML escaped and that
Chris@0 559 // loses safeness when imploded. The help text is used in #description
Chris@0 560 // and therefore XSS admin filtered by default. Escaped HTML is not
Chris@0 561 // altered by XSS filtering, therefore it is safe to just concatenate the
Chris@0 562 // strings. Afterwards we mark the entire string as safe, so it won't be
Chris@0 563 // escaped, no matter where it is used.
Chris@0 564 // Considering the dual use of this help data (both as metadata and as
Chris@0 565 // help text), other patterns such as use of #markup would not be correct
Chris@0 566 // here.
Chris@0 567 $data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
Chris@0 568 }
Chris@0 569
Chris@0 570 $keys = array_keys($field_columns);
Chris@0 571 $real_field = reset($keys);
Chris@0 572 $data[$table_alias][$field_alias]['field'] = [
Chris@0 573 'table' => $table,
Chris@0 574 'id' => 'field',
Chris@0 575 'field_name' => $field_name,
Chris@0 576 'entity_type' => $entity_type_id,
Chris@0 577 // Provide a real field for group by.
Chris@0 578 'real field' => $field_alias . '_' . $real_field,
Chris@0 579 'additional fields' => $add_fields,
Chris@0 580 // Default the element type to div, let the UI change it if necessary.
Chris@0 581 'element type' => 'div',
Chris@0 582 'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION,
Chris@0 583 ];
Chris@0 584 }
Chris@0 585
Chris@0 586 // Expose data for each field property individually.
Chris@0 587 foreach ($field_columns as $column => $attributes) {
Chris@0 588 $allow_sort = TRUE;
Chris@0 589
Chris@0 590 // Identify likely filters and arguments for each column based on field type.
Chris@0 591 switch ($attributes['type']) {
Chris@0 592 case 'int':
Chris@0 593 case 'mediumint':
Chris@0 594 case 'tinyint':
Chris@0 595 case 'bigint':
Chris@0 596 case 'serial':
Chris@0 597 case 'numeric':
Chris@0 598 case 'float':
Chris@0 599 $filter = 'numeric';
Chris@0 600 $argument = 'numeric';
Chris@0 601 $sort = 'standard';
Chris@0 602 if ($field_storage->getType() == 'boolean') {
Chris@0 603 $filter = 'boolean';
Chris@0 604 }
Chris@0 605 break;
Chris@0 606 case 'blob':
Chris@18 607 // It does not make sense to sort by blob.
Chris@0 608 $allow_sort = FALSE;
Chris@0 609 default:
Chris@0 610 $filter = 'string';
Chris@0 611 $argument = 'string';
Chris@0 612 $sort = 'standard';
Chris@0 613 break;
Chris@0 614 }
Chris@0 615
Chris@0 616 if (count($field_columns) == 1 || $column == 'value') {
Chris@0 617 $title = t('@label (@name)', ['@label' => $label, '@name' => $field_name]);
Chris@0 618 $title_short = $label;
Chris@0 619 }
Chris@0 620 else {
Chris@0 621 $title = t('@label (@name:@column)', ['@label' => $label, '@name' => $field_name, '@column' => $column]);
Chris@0 622 $title_short = t('@label:@column', ['@label' => $label, '@column' => $column]);
Chris@0 623 }
Chris@0 624
Chris@0 625 // Expose data for the property.
Chris@0 626 foreach ($field_tables as $type => $table_info) {
Chris@0 627 $table = $table_info['table'];
Chris@0 628 $table_alias = $table_info['alias'];
Chris@0 629
Chris@0 630 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
Chris@0 631 $group = $group_name;
Chris@0 632 }
Chris@0 633 else {
Chris@0 634 $group = t('@group (historical data)', ['@group' => $group_name]);
Chris@0 635 }
Chris@0 636 $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column);
Chris@0 637
Chris@0 638 // Load all the fields from the table by default.
Chris@0 639 $additional_fields = $table_mapping->getAllColumns($table);
Chris@0 640
Chris@0 641 $data[$table_alias][$column_real_name] = [
Chris@0 642 'group' => $group,
Chris@0 643 'title' => $title,
Chris@0 644 'title short' => $title_short,
Chris@0 645 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
Chris@0 646 ];
Chris@0 647
Chris@0 648 // Go through and create a list of aliases for all possible combinations of
Chris@0 649 // entity type + name.
Chris@0 650 $aliases = [];
Chris@0 651 $also_known = [];
Chris@0 652 foreach ($all_labels as $label_name => $true) {
Chris@0 653 if ($label != $label_name) {
Chris@0 654 if (count($field_columns) == 1 || $column == 'value') {
Chris@0 655 $alias_title = t('@label (@name)', ['@label' => $label_name, '@name' => $field_name]);
Chris@0 656 }
Chris@0 657 else {
Chris@0 658 $alias_title = t('@label (@name:@column)', ['@label' => $label_name, '@name' => $field_name, '@column' => $column]);
Chris@0 659 }
Chris@0 660 $aliases[] = [
Chris@0 661 'group' => $group_name,
Chris@0 662 'title' => $alias_title,
Chris@0 663 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $title]),
Chris@0 664 ];
Chris@0 665 $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $title]);
Chris@0 666 }
Chris@0 667 }
Chris@0 668 if ($aliases) {
Chris@0 669 $data[$table_alias][$column_real_name]['aliases'] = $aliases;
Chris@0 670 // The $also_known variable contains markup that is HTML escaped and
Chris@0 671 // that loses safeness when imploded. The help text is used in
Chris@0 672 // #description and therefore XSS admin filtered by default. Escaped
Chris@0 673 // HTML is not altered by XSS filtering, therefore it is safe to just
Chris@0 674 // concatenate the strings. Afterwards we mark the entire string as
Chris@0 675 // safe, so it won't be escaped, no matter where it is used.
Chris@0 676 // Considering the dual use of this help data (both as metadata and as
Chris@0 677 // help text), other patterns such as use of #markup would not be
Chris@0 678 // correct here.
Chris@0 679 $data[$table_alias][$column_real_name]['help'] = Markup::create($data[$table_alias][$column_real_name]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
Chris@0 680 }
Chris@0 681
Chris@0 682 $data[$table_alias][$column_real_name]['argument'] = [
Chris@0 683 'field' => $column_real_name,
Chris@0 684 'table' => $table,
Chris@0 685 'id' => $argument,
Chris@0 686 'additional fields' => $additional_fields,
Chris@0 687 'field_name' => $field_name,
Chris@0 688 'entity_type' => $entity_type_id,
Chris@0 689 'empty field name' => t('- No value -'),
Chris@0 690 ];
Chris@0 691 $data[$table_alias][$column_real_name]['filter'] = [
Chris@0 692 'field' => $column_real_name,
Chris@0 693 'table' => $table,
Chris@0 694 'id' => $filter,
Chris@0 695 'additional fields' => $additional_fields,
Chris@0 696 'field_name' => $field_name,
Chris@0 697 'entity_type' => $entity_type_id,
Chris@0 698 'allow empty' => TRUE,
Chris@0 699 ];
Chris@0 700 if (!empty($allow_sort)) {
Chris@0 701 $data[$table_alias][$column_real_name]['sort'] = [
Chris@0 702 'field' => $column_real_name,
Chris@0 703 'table' => $table,
Chris@0 704 'id' => $sort,
Chris@0 705 'additional fields' => $additional_fields,
Chris@0 706 'field_name' => $field_name,
Chris@0 707 'entity_type' => $entity_type_id,
Chris@0 708 ];
Chris@0 709 }
Chris@0 710
Chris@0 711 // Set click sortable if there is a field definition.
Chris@0 712 if (isset($data[$table_alias][$field_name]['field'])) {
Chris@0 713 $data[$table_alias][$field_name]['field']['click sortable'] = $allow_sort;
Chris@0 714 }
Chris@0 715
Chris@0 716 // Expose additional delta column for multiple value fields.
Chris@0 717 if ($field_storage->isMultiple()) {
Chris@0 718 $title_delta = t('@label (@name:delta)', ['@label' => $label, '@name' => $field_name]);
Chris@0 719 $title_short_delta = t('@label:delta', ['@label' => $label]);
Chris@0 720
Chris@0 721 $data[$table_alias]['delta'] = [
Chris@0 722 'group' => $group,
Chris@0 723 'title' => $title_delta,
Chris@0 724 'title short' => $title_short_delta,
Chris@0 725 'help' => t('Delta - Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
Chris@0 726 ];
Chris@0 727 $data[$table_alias]['delta']['field'] = [
Chris@0 728 'id' => 'numeric',
Chris@0 729 ];
Chris@0 730 $data[$table_alias]['delta']['argument'] = [
Chris@0 731 'field' => 'delta',
Chris@0 732 'table' => $table,
Chris@0 733 'id' => 'numeric',
Chris@0 734 'additional fields' => $additional_fields,
Chris@0 735 'empty field name' => t('- No value -'),
Chris@0 736 'field_name' => $field_name,
Chris@0 737 'entity_type' => $entity_type_id,
Chris@0 738 ];
Chris@0 739 $data[$table_alias]['delta']['filter'] = [
Chris@0 740 'field' => 'delta',
Chris@0 741 'table' => $table,
Chris@0 742 'id' => 'numeric',
Chris@0 743 'additional fields' => $additional_fields,
Chris@0 744 'field_name' => $field_name,
Chris@0 745 'entity_type' => $entity_type_id,
Chris@0 746 'allow empty' => TRUE,
Chris@0 747 ];
Chris@0 748 $data[$table_alias]['delta']['sort'] = [
Chris@0 749 'field' => 'delta',
Chris@0 750 'table' => $table,
Chris@0 751 'id' => 'standard',
Chris@0 752 'additional fields' => $additional_fields,
Chris@0 753 'field_name' => $field_name,
Chris@0 754 'entity_type' => $entity_type_id,
Chris@0 755 ];
Chris@0 756 }
Chris@0 757 }
Chris@0 758 }
Chris@0 759
Chris@0 760 return $data;
Chris@0 761 }
Chris@0 762
Chris@0 763 /**
Chris@0 764 * Implements hook_field_views_data().
Chris@0 765 *
Chris@0 766 * The function implements the hook in behalf of 'core' because it adds a
Chris@0 767 * relationship and a reverse relationship to entity_reference field type, which
Chris@0 768 * is provided by core.
Chris@0 769 */
Chris@0 770 function core_field_views_data(FieldStorageConfigInterface $field_storage) {
Chris@0 771 $data = views_field_default_views_data($field_storage);
Chris@0 772
Chris@0 773 // The code below only deals with the Entity reference field type.
Chris@0 774 if ($field_storage->getType() != 'entity_reference') {
Chris@0 775 return $data;
Chris@0 776 }
Chris@0 777
Chris@0 778 $entity_manager = \Drupal::entityManager();
Chris@0 779 $entity_type_id = $field_storage->getTargetEntityTypeId();
Chris@0 780 /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
Chris@0 781 $table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping();
Chris@0 782
Chris@0 783 foreach ($data as $table_name => $table_data) {
Chris@0 784 // Add a relationship to the target entity type.
Chris@0 785 $target_entity_type_id = $field_storage->getSetting('target_type');
Chris@0 786 $target_entity_type = $entity_manager->getDefinition($target_entity_type_id);
Chris@0 787 $entity_type_id = $field_storage->getTargetEntityTypeId();
Chris@0 788 $entity_type = $entity_manager->getDefinition($entity_type_id);
Chris@0 789 $target_base_table = $target_entity_type->getDataTable() ?: $target_entity_type->getBaseTable();
Chris@0 790 $field_name = $field_storage->getName();
Chris@0 791
Chris@0 792 // Provide a relationship for the entity type with the entity reference
Chris@0 793 // field.
Chris@0 794 $args = [
Chris@0 795 '@label' => $target_entity_type->getLabel(),
Chris@0 796 '@field_name' => $field_name,
Chris@0 797 ];
Chris@0 798 $data[$table_name][$field_name]['relationship'] = [
Chris@0 799 'title' => t('@label referenced from @field_name', $args),
Chris@0 800 'label' => t('@field_name: @label', $args),
Chris@0 801 'group' => $entity_type->getLabel(),
Chris@0 802 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $field_storage->getBundles())]),
Chris@0 803 'id' => 'standard',
Chris@0 804 'base' => $target_base_table,
Chris@0 805 'entity type' => $target_entity_type_id,
Chris@0 806 'base field' => $target_entity_type->getKey('id'),
Chris@0 807 'relationship field' => $field_name . '_target_id',
Chris@0 808 ];
Chris@0 809
Chris@0 810 // Provide a reverse relationship for the entity type that is referenced by
Chris@0 811 // the field.
Chris@0 812 $args['@entity'] = $entity_type->getLabel();
Chris@0 813 $args['@label'] = $target_entity_type->getLowercaseLabel();
Chris@0 814 $pseudo_field_name = 'reverse__' . $entity_type_id . '__' . $field_name;
Chris@0 815 $data[$target_base_table][$pseudo_field_name]['relationship'] = [
Chris@0 816 'title' => t('@entity using @field_name', $args),
Chris@0 817 'label' => t('@field_name', ['@field_name' => $field_name]),
Chris@0 818 'group' => $target_entity_type->getLabel(),
Chris@0 819 'help' => t('Relate each @entity with a @field_name set to the @label.', $args),
Chris@0 820 'id' => 'entity_reverse',
Chris@0 821 'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
Chris@0 822 'entity_type' => $entity_type_id,
Chris@0 823 'base field' => $entity_type->getKey('id'),
Chris@0 824 'field_name' => $field_name,
Chris@0 825 'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
Chris@0 826 'field field' => $field_name . '_target_id',
Chris@0 827 'join_extra' => [
Chris@0 828 [
Chris@0 829 'field' => 'deleted',
Chris@0 830 'value' => 0,
Chris@0 831 'numeric' => TRUE,
Chris@0 832 ],
Chris@0 833 ],
Chris@0 834 ];
Chris@0 835 }
Chris@0 836
Chris@0 837 return $data;
Chris@0 838 }