entity.api.php 26.7 KB
Newer Older
1
2
3
4
5
6
7
<?php

/**
 * @file
 * Hooks provided the Entity module.
 */

8
use Drupal\Core\Entity\ContentEntityInterface;
9
use Drupal\Core\Field\FieldDefinition;
10

11
12
13
14
15
/**
 * @addtogroup hooks
 * @{
 */

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
 * Control entity operation access.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity to check access to.
 * @param string $operation
 *   The operation that is to be performed on $entity.
 * @param \Drupal\Core\Session\AccountInterface $account
 *    The account trying to access the entity.
 * @param string $langcode
 *    The code of the language $entity is accessed in.
 *
 * @return bool|null
 *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
 *   deny access.
 *
 * @see \Drupal\Core\Entity\EntityAccessController
 */
function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
  return NULL;
}

/**
 * Control entity operation access for a specific entity type.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity to check access to.
 * @param string $operation
 *   The operation that is to be performed on $entity.
 * @param \Drupal\Core\Session\AccountInterface $account
 *    The account trying to access the entity.
 * @param string $langcode
 *    The code of the language $entity is accessed in.
 *
 * @return bool|null
 *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
 *   deny access.
 *
 * @see \Drupal\Core\Entity\EntityAccessController
 */
function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
  return NULL;
}

/**
 * Control entity create access.
 *
 * @param \Drupal\Core\Session\AccountInterface $account
 *    The account trying to access the entity.
 * @param string $langcode
 *    The code of the language $entity is accessed in.
 *
 * @return bool|null
 *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
 *   deny access.
 *
 * @see \Drupal\Core\Entity\EntityAccessController
 */
function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) {
  return NULL;
}

/**
 * Control entity create access for a specific entity type.
 *
 * @param \Drupal\Core\Session\AccountInterface $account
 *    The account trying to access the entity.
 * @param string $langcode
 *    The code of the language $entity is accessed in.
 *
 * @return bool|null
 *   A boolean to explicitly allow or deny access, or NULL to neither allow nor
 *   deny access.
 *
 * @see \Drupal\Core\Entity\EntityAccessController
 */
function hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) {
  return NULL;
}

96
/**
97
98
 * Add to entity type definitions.
 *
99
100
 * Modules may implement this hook to add information to defined entity types,
 * as defined in \Drupal\Core\Entity\EntityTypeInterface.
101
 *
102
 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
103
104
105
106
 *   An associative array of all entity type definitions, keyed by the entity
 *   type name. Passed by reference.
 *
 * @see \Drupal\Core\Entity\Entity
107
 * @see \Drupal\Core\Entity\EntityTypeInterface
108
109
 */
function hook_entity_info(&$entity_info) {
110
  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
111
  // Add a form controller for a custom node form without overriding the default
112
  // node form. To override the default node form, use hook_entity_info_alter().
113
  $entity_info['node']->setFormClass('mymodule_foo', 'Drupal\mymodule\NodeFooFormController');
114
115
}

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
 * Alter the entity type definitions.
 *
 * Modules may implement this hook to alter the information that defines an
 * entity type. All properties that are available in
 * \Drupal\Core\Entity\Annotation\EntityType and all the ones additionally
 * provided by modules can be altered here.
 *
 * Do not use this hook to add information to entity types, unless you are just
 * filling-in default values. Use hook_entity_info() instead.
 *
 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_info
 *   An associative array of all entity type definitions, keyed by the entity
 *   type name. Passed by reference.
 *
 * @see \Drupal\Core\Entity\Entity
 * @see \Drupal\Core\Entity\EntityTypeInterface
 */
function hook_entity_info_alter(&$entity_info) {
  /** @var $entity_info \Drupal\Core\Entity\EntityTypeInterface[] */
  // Set the controller class for nodes to an alternate implementation of the
  // Drupal\Core\Entity\EntityStorageControllerInterface interface.
138
  $entity_info['node']->setStorageClass('Drupal\mymodule\MyCustomNodeStorageController');
139
140
}

141
142
143
144
145
146
147
148
149
150
/**
 * Alter the view modes for entity types.
 *
 * @param array $view_modes
 *   An array of view modes, keyed first by entity type, then by view mode name.
 *
 * @see entity_get_view_modes()
 * @see hook_entity_view_mode_info()
 */
function hook_entity_view_mode_info_alter(&$view_modes) {
151
  $view_modes['user']['full']['status'] = TRUE;
152
153
154
155
156
157
158
159
160
161
162
163
164
}

/**
 * Describe the bundles for entity types.
 *
 * @return array
 *   An associative array of all entity bundles, keyed by the entity
 *   type name, and then the bundle name, with the following keys:
 *   - label: The human-readable name of the bundle.
 *   - uri_callback: The same as the 'uri_callback' key defined for the entity
 *     type in the EntityManager, but for the bundle only. When determining
 *     the URI of an entity, if a 'uri_callback' is defined for both the
 *     entity type and the bundle, the one for the bundle is used.
165
166
 *   - translatable: (optional) A boolean value specifying whether this bundle
 *     has translation support enabled. Defaults to FALSE.
167
168
169
170
171
 *
 * @see entity_get_bundles()
 * @see hook_entity_bundle_info_alter()
 */
function hook_entity_bundle_info() {
172
  $bundles['user']['user']['label'] = t('User');
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  return $bundles;
}

/**
 * Alter the bundles for entity types.
 *
 * @param array $bundles
 *   An array of bundles, keyed first by entity type, then by bundle name.
 *
 * @see entity_get_bundles()
 * @see hook_entity_bundle_info()
 */
function hook_entity_bundle_info_alter(&$bundles) {
  $bundles['user']['user']['label'] = t('Full account');
187
188
}

189
190
191
192
193
194
195
196
197
198
199
200
201
/**
 * Act on entity_bundle_create().
 *
 * This hook is invoked after the operation has been performed.
 *
 * @param string $entity_type
 *   The type of $entity; e.g. 'node' or 'user'.
 * @param string $bundle
 *   The name of the bundle.
 */
function hook_entity_bundle_create($entity_type, $bundle) {
  // When a new bundle is created, the menu needs to be rebuilt to add the
  // Field UI menu item tabs.
202
  \Drupal::service('router.builder')->setRebuildNeeded();
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
}

/**
 * Act on entity_bundle_rename().
 *
 * This hook is invoked after the operation has been performed.
 *
 * @param string $entity_type
 *   The entity type to which the bundle is bound.
 * @param string $bundle_old
 *   The previous name of the bundle.
 * @param string $bundle_new
 *   The new name of the bundle.
 */
function hook_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
  // Update the settings associated with the bundle in my_module.settings.
219
  $config = \Drupal::config('my_module.settings');
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
  $bundle_settings = $config->get('bundle_settings');
  if (isset($bundle_settings[$entity_type][$bundle_old])) {
    $bundle_settings[$entity_type][$bundle_new] = $bundle_settings[$entity_type][$bundle_old];
    unset($bundle_settings[$entity_type][$bundle_old]);
    $config->set('bundle_settings', $bundle_settings);
  }
}

/**
 * Act on entity_bundle_delete().
 *
 * This hook is invoked after the operation has been performed.
 *
 * @param string $entity_type
 *   The type of entity; for example, 'node' or 'user'.
 * @param string $bundle
 *   The bundle that was just deleted.
 */
function hook_entity_bundle_delete($entity_type, $bundle) {
  // Remove the settings associated with the bundle in my_module.settings.
240
  $config = \Drupal::config('my_module.settings');
241
242
243
244
245
246
247
  $bundle_settings = $config->get('bundle_settings');
  if (isset($bundle_settings[$entity_type][$bundle])) {
    unset($bundle_settings[$entity_type][$bundle]);
    $config->set('bundle_settings', $bundle_settings);
  }
}

248
249
250
251
252
253
254
255
256
257
/**
 * Act on a newly created entity.
 *
 * This hook runs after a new entity object has just been instantiated. It can
 * be used to set initial values, e.g. to provide defaults.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity object.
 */
function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
258
  if ($entity instanceof ContentEntityInterface && !$entity->foo->value) {
259
260
261
262
    $entity->foo->value = 'some_initial_value';
  }
}

263
264
265
266
267
268
/**
 * Act on entities when loaded.
 *
 * This is a generic load hook called for all entity types loaded via the
 * entity API.
 *
269
 * @param array $entities
270
 *   The entities keyed by entity ID.
271
 * @param string $entity_type
272
273
 *   The type of entities being loaded (i.e. node, user, comment).
 */
274
function hook_entity_load($entities, $entity_type) {
275
  foreach ($entities as $entity) {
276
    $entity->foo = mymodule_add_something($entity);
277
278
279
280
281
282
  }
}

/**
 * Act on an entity before it is about to be created or updated.
 *
283
 * @param \Drupal\Core\Entity\EntityInterface $entity
284
285
 *   The entity object.
 */
286
function hook_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
287
288
289
290
  $entity->changed = REQUEST_TIME;
}

/**
291
292
293
294
 * Respond to creation of a new entity.
 *
 * This hook runs once the entity has been stored. Note that hook
 * implementations may not alter the stored entity data.
295
 *
296
 * @param \Drupal\Core\Entity\EntityInterface $entity
297
298
 *   The entity object.
 */
299
function hook_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
300
301
302
  // Insert the new entity into a fictional table of all entities.
  db_insert('example_entity')
    ->fields(array(
303
      'type' => $entity->getEntityTypeId(),
304
      'id' => $entity->id(),
305
306
307
308
309
310
311
      'created' => REQUEST_TIME,
      'updated' => REQUEST_TIME,
    ))
    ->execute();
}

/**
312
313
314
315
 * Respond to updates to an entity.
 *
 * This hook runs once the entity storage has been updated. Note that hook
 * implementations may not alter the stored entity data.
316
 *
317
 * @param \Drupal\Core\Entity\EntityInterface $entity
318
319
 *   The entity object.
 */
320
function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
321
322
323
324
325
  // Update the entity's entry in a fictional table of all entities.
  db_update('example_entity')
    ->fields(array(
      'updated' => REQUEST_TIME,
    ))
326
    ->condition('type', $entity->getEntityTypeId())
327
    ->condition('id', $entity->id())
328
329
330
    ->execute();
}

331
/**
332
333
334
335
 * Respond to creation of a new entity translation.
 *
 * This hook runs once the entity translation has been stored. Note that hook
 * implementations may not alter the stored entity translation data.
336
337
338
339
340
341
342
343
344
345
346
347
348
 *
 * @param \Drupal\Core\Entity\EntityInterface $translation
 *   The entity object of the translation just stored.
 */
function hook_entity_translation_insert(\Drupal\Core\Entity\EntityInterface $translation) {
  $variables = array(
    '@language' => $translation->language()->name,
    '@label' => $translation->getUntranslated()->label(),
  );
  watchdog('example', 'The @language translation of @label has just been stored.', $variables);
}

/**
349
350
351
 * Respond to entity translation deletion.
 *
 * This hook runs once the entity translation has been deleted from storage.
352
353
354
355
356
357
358
359
360
361
362
363
364
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The original entity object.
 */
function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $translation) {
  $languages = language_list();
  $variables = array(
    '@language' => $languages[$langcode]->name,
    '@label' => $entity->label(),
  );
  watchdog('example', 'The @language translation of @label has just been deleted.', $variables);
}

365
/**
366
367
 * Act before entity deletion.
 *
368
 * @param \Drupal\Core\Entity\EntityInterface $entity
369
370
 *   The entity object for the entity that is about to be deleted.
 */
371
function hook_entity_predelete(Drupal\Core\Entity\EntityInterface $entity) {
372
373
  // Count references to this entity in a custom table before they are removed
  // upon entity deletion.
374
  $id = $entity->id();
375
  $type = $entity->getEntityTypeId();
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
  $count = db_select('example_entity_data')
    ->condition('type', $type)
    ->condition('id', $id)
    ->countQuery()
    ->execute()
    ->fetchField();

  // Log the count in a table that records this statistic for deleted entities.
  $ref_count_record = (object) array(
    'count' => $count,
    'type' => $type,
    'id' => $id,
  );
  drupal_write_record('example_deleted_entity_statistics', $ref_count_record);
}

/**
 * Respond to entity deletion.
 *
395
 * This hook runs once the entity has been deleted from the storage.
396
 *
397
 * @param \Drupal\Core\Entity\EntityInterface $entity
398
 *   The entity object for the entity that has been deleted.
399
 */
400
function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) {
401
402
  // Delete the entity's entry from a fictional table of all entities.
  db_delete('example_entity')
403
    ->condition('type', $entity->getEntityTypeId())
404
    ->condition('id', $entity->id())
405
406
407
    ->execute();
}

408
409
410
/**
 * Respond to entity revision deletion.
 *
411
 * This hook runs once the entity revision has been deleted from the storage.
412
 *
413
 * @param \Drupal\Core\Entity\EntityInterface $entity
414
415
416
417
418
419
 *   The entity object for the entity revision that has been deleted.
 */
function hook_entity_revision_delete(Drupal\Core\Entity\EntityInterface $entity) {
  // @todo: code example
}

420
/**
421
 * Alter or execute an Drupal\Core\Entity\Query\EntityQueryInterface.
422
 *
423
 * @param \Drupal\Core\Entity\Query\QueryInterface $query
424
425
426
427
428
429
430
 *   Note the $query->altered attribute which is TRUE in case the query has
 *   already been altered once. This happens with cloned queries.
 *   If there is a pager, then such a cloned query will be executed to count
 *   all elements. This query can be detected by checking for
 *   ($query->pager && $query->count), allowing the driver to return 0 from
 *   the count query and disable the pager.
 */
431
432
function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query) {
  // @todo: code example.
433
434
435
436
437
}

/**
 * Act on entities being assembled before rendering.
 *
438
 * @param \Drupal\Core\Entity\EntityInterface $entity
439
 *   The entity object.
440
 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
441
442
 *   The entity_display object holding the display options configured for the
 *   entity components.
443
444
445
446
447
448
449
450
451
452
453
454
455
456
 * @param $view_mode
 *   The view mode the entity is rendered in.
 * @param $langcode
 *   The language code used for rendering.
 *
 * The module may add elements to $entity->content prior to rendering. The
 * structure of $entity->content is a renderable array as expected by
 * drupal_render().
 *
 * @see hook_entity_view_alter()
 * @see hook_comment_view()
 * @see hook_node_view()
 * @see hook_user_view()
 */
457
function hook_entity_view(\Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
458
459
460
461
462
463
464
465
466
  // Only do the extra work if the component is configured to be displayed.
  // This assumes a 'mymodule_addition' extra field has been defined for the
  // entity bundle in hook_field_extra_fields().
  if ($display->getComponent('mymodule_addition')) {
    $entity->content['mymodule_addition'] = array(
      '#markup' => mymodule_addition($entity),
      '#theme' => 'mymodule_my_additional_field',
    );
  }
467
468
469
470
471
472
473
474
475
476
477
}

/**
 * Alter the results of ENTITY_view().
 *
 * This hook is called after the content has been assembled in a structured
 * array and may be used for doing processing which requires that the complete
 * entity content structure has been built.
 *
 * If a module wishes to act on the rendered HTML of the entity rather than the
 * structured content array, it may use this hook to add a #post_render
478
 * callback. Alternatively, it could also implement hook_preprocess_HOOK() for
479
 * the particular entity type template, if there is one (e.g., node.html.twig).
480
481
482
483
 * See drupal_render() and theme() for details.
 *
 * @param $build
 *   A renderable array representing the entity content.
484
 * @param \Drupal\Core\Entity\EntityInterface $entity
485
 *   The entity object being rendered.
486
 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
487
488
 *   The entity_display object holding the display options configured for the
 *   entity components.
489
490
491
492
493
494
495
 *
 * @see hook_entity_view()
 * @see hook_comment_view_alter()
 * @see hook_node_view_alter()
 * @see hook_taxonomy_term_view_alter()
 * @see hook_user_view_alter()
 */
496
function hook_entity_view_alter(&$build, Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
497
498
499
500
501
502
503
504
505
506
507
508
509
  if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
    // Change its weight.
    $build['an_additional_field']['#weight'] = -10;

    // Add a #post_render callback to act on the rendered HTML of the entity.
    $build['#post_render'][] = 'my_module_node_post_render';
  }
}

/**
 * Act on entities as they are being prepared for view.
 *
 * Allows you to operate on multiple entities as they are being prepared for
510
 * view. Only use this if attaching the data during the entity loading phase
511
512
 * is not appropriate, for example when attaching other 'entity' style objects.
 *
513
514
 * @param string $entity_type
 *   The type of entities being viewed (i.e. node, user, comment).
515
516
517
518
519
520
521
 * @param array $entities
 *   The entities keyed by entity ID.
 * @param array $display
 *   The array of entity_display objects holding the display options configured
 *   for the entity components, keyed by bundle name.
 * @param string $view_mode
 *   The view mode.
522
 */
523
function hook_entity_prepare_view($entity_type, array $entities, array $displays, $view_mode) {
524
  // Load a specific node into the user object for later theming.
525
  if (!empty($entities) && $entity_type == 'user') {
526
527
528
529
530
531
532
533
534
535
536
537
538
539
    // Only do the extra work if the component is configured to be
    // displayed. This assumes a 'mymodule_addition' extra field has been
    // defined for the entity bundle in hook_field_extra_fields().
    $ids = array();
    foreach ($entities as $id => $entity) {
      if ($displays[$entity->bundle()]->getComponent('mymodule_addition')) {
        $ids[] = $id;
      }
    }
    if ($ids) {
      $nodes = mymodule_get_user_nodes($ids);
      foreach ($ids as $id) {
        $entities[$id]->user_node = $nodes[$id];
      }
540
541
542
    }
  }
}
543
544
545
546
547
548

/**
 * Change the view mode of an entity that is being displayed.
 *
 * @param string $view_mode
 *   The view_mode that is to be used to display the entity.
549
 * @param \Drupal\Core\Entity\EntityInterface $entity
550
551
552
553
554
 *   The entity that is being viewed.
 * @param array $context
 *   Array with additional context information, currently only contains the
 *   langcode the entity is viewed in.
 */
555
function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInterface $entity, $context) {
556
  // For nodes, change the view mode when it is teaser.
557
  if ($entity->getEntityTypeId() == 'node' && $view_mode == 'teaser') {
558
559
560
    $view_mode = 'my_custom_view_mode';
  }
}
561

562
563
564
/**
 * Alters the settings used for displaying an entity.
 *
565
 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
566
567
568
569
570
571
572
573
 *   The entity_display object that will be used to display the entity
 *   components.
 * @param array $context
 *   An associative array containing:
 *   - entity_type: The entity type, e.g., 'node' or 'user'.
 *   - bundle: The bundle, e.g., 'page' or 'article'.
 *   - view_mode: The view mode, e.g. 'full', 'teaser'...
 */
574
function hook_entity_display_alter(\Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, array $context) {
575
576
  // Leave field labels out of the search index.
  if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') {
577
578
579
580
    foreach ($display->getComponents() as $name => $options) {
      if (isset($options['label'])) {
        $options['label'] = 'hidden';
        $display->setComponent($name, $options);
581
582
583
584
585
      }
    }
  }
}

586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
/**
 * Acts on an entity object about to be shown on an entity form.
 *
 * This can be typically used to pre-fill entity values or change the form state
 * before the entity form is built. It is invoked just once when first building
 * the entity form. Rebuilds will not trigger a new invocation.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity that is about to be shown on the form.
 * @param $form_display
 *   The current form display.
 * @param $operation
 *   The current operation.
 * @param array $form_state
 *   An associative array containing the current state of the form.
 *
 * @see \Drupal\Core\Entity\EntityFormController::prepareEntity()
 */
function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $form_display, $operation, array &$form_state) {
  if ($operation == 'edit') {
    $entity->label->value = 'Altered label';
    $form_state['mymodule']['label_altered'] = TRUE;
  }
}

611
612
613
/**
 * Alters the settings used for displaying an entity form.
 *
614
 * @param \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
615
616
617
618
619
620
621
622
 *   The entity_form_display object that will be used to display the entity form
 *   components.
 * @param array $context
 *   An associative array containing:
 *   - entity_type: The entity type, e.g., 'node' or 'user'.
 *   - bundle: The bundle, e.g., 'page' or 'article'.
 *   - form_mode: The form mode, e.g. 'default', 'profile', 'register'...
 */
623
function hook_entity_form_display_alter(\Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display, array $context) {
624
625
626
627
628
629
630
631
  // Hide the 'user_picture' field from the register form.
  if ($context['entity_type'] == 'user' && $context['form_mode'] == 'register') {
    $form_display->setComponent('user_picture', array(
      'type' => 'hidden',
    ));
  }
}

632
/**
633
 * Define custom entity fields.
634
635
 *
 * @param string $entity_type
636
 *   The entity type for which to define entity fields.
637
638
 *
 * @return array
639
 *   An array of entity field information having the following optional entries:
640
641
 *   - definitions: An array of field definitions to add to all entities of this
 *     type, keyed by field name.
642
643
644
645
646
647
 *   - optional: An array of field definitions for optional entity fields, keyed
 *     by field name. Optional fields are fields that only exist for certain
 *     bundles of the entity type.
 *   - bundle map: An array keyed by bundle name, containing the names of
 *     optional fields that entities of this bundle have.
 *
648
 * @see hook_entity_field_info_alter()
649
 * @see \Drupal\Core\Field\FieldDefinitionInterface
650
 * @see \Drupal\Core\Entity\EntityManagerInterface::getFieldDefinitions()
651
 * @see \Drupal\Core\TypedData\TypedDataManager::create()
652
653
654
655
 */
function hook_entity_field_info($entity_type) {
  if (mymodule_uses_entity_type($entity_type)) {
    $info = array();
656
657
658
659
660
661
    $info['definitions']['mymodule_text'] = FieldDefinition::create('string')
      ->setLabel(t('The text'))
      ->setDescription(t('A text property added by mymodule.'))
      ->setComputed(TRUE)
      ->setClass('\Drupal\mymodule\EntityComputedText');

662
663
    if ($entity_type == 'node') {
      // Add a property only to entities of the 'article' bundle.
664
665
666
667
668
      $info['optional']['mymodule_text_more'] = FieldDefinition::create('string')
        ->setLabel(t('More text'))
        ->setComputed(TRUE)
        ->setClass('\Drupal\mymodule\EntityComputedMoreText');

669
670
671
672
673
674
675
      $info['bundle map']['article'][0] = 'mymodule_text_more';
    }
    return $info;
  }
}

/**
676
 * Alter defined entity fields.
677
678
 *
 * @param array $info
679
 *   The entity field info array as returned by hook_entity_field_info().
680
 * @param string $entity_type
681
 *   The entity type for which entity fields are defined.
682
683
684
685
686
 *
 * @see hook_entity_field_info()
 */
function hook_entity_field_info_alter(&$info, $entity_type) {
  if (!empty($info['definitions']['mymodule_text'])) {
687
688
    // Alter the mymodule_text field to use a custom class.
    $info['definitions']['mymodule_text']->setClass('\Drupal\anothermodule\EntityComputedText');
689
690
  }
}
691

692
693
694
695
696
/**
 * Alter entity operations.
 *
 * @param array $operations
 *   Operations array as returned by
697
 *   \Drupal\Core\Entity\EntityListControllerInterface::getOperations().
698
699
700
701
702
703
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   The entity on which the linked operations will be performed.
 */
function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\EntityInterface $entity) {
  $operations['translate'] = array(
    'title' => t('Translate'),
704
    'href' => $entity->getSystemPath() . '/translate',
705
706
707
708
    'weight' => 50,
  );
}

709
710
711
/**
 * Control access to fields.
 *
712
 * This hook is invoked from
713
 * \Drupal\Core\Entity\EntityAccessController::fieldAccess() to let modules
714
 * grant or deny operations on fields.
715
716
717
 *
 * @param string $operation
 *   The operation to be performed. See
718
 *   \Drupal\Core\Access\AccessibleInterface::access() for possible values.
719
 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
720
 *   The field definition.
721
 * @param \Drupal\Core\Session\AccountInterface $account
722
 *   The user account to check.
723
 * @param \Drupal\Core\Field\FieldItemListInterface $items
724
725
 *   (optional) The entity field object on which the operation is to be
 *   performed.
726
 *
727
 * @return bool|null
728
729
730
 *   TRUE if access should be allowed, FALSE if access should be denied and NULL
 *   if the implementation has no opinion.
 */
731
function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) {
732
  if ($field_definition->getName() == 'field_of_interest' && $operation == 'edit') {
733
734
735
736
737
    return user_access('update field of interest', $account);
  }
}

/**
738
 * Alters the default access behavior for a given field.
739
740
741
742
743
744
745
746
747
748
749
 *
 * Use this hook to override access grants from another module. Note that the
 * original default access flag is masked under the ':default' key.
 *
 * @param array $grants
 *   An array of grants gathered by hook_entity_field_access(). The array is
 *   keyed by the module that defines the field's access control; the values are
 *   grant responses for each module (Boolean or NULL).
 * @param array $context
 *   Context array on the performed operation with the following keys:
 *   - operation: The operation to be performed (string).
750
 *   - field_definition: The field definition object
751
 *     (\Drupal\Core\Field\FieldDefinitionInterface)
752
 *   - account: The user account to check access for
753
 *     (Drupal\user\Entity\User).
754
 *   - items: (optional) The entity field items
755
 *     (\Drupal\Core\Field\FieldItemListInterface).
756
757
 */
function hook_entity_field_access_alter(array &$grants, array $context) {
758
  $field_definition = $context['field_definition'];
759
  if ($field_definition->getName() == 'field_of_interest' && $grants['node'] === FALSE) {
760
761
762
763
764
765
766
767
    // Override node module's restriction to no opinion. We don't want to
    // provide our own access hook, we only want to take out node module's part
    // in the access handling of this field. We also don't want to switch node
    // module's grant to TRUE, because the grants of other modules should still
    // decide on their own if this field is accessible or not.
    $grants['node'] = NULL;
  }
}