Updated to Drupal 8.6.4, which is PHP 7.3 friendly. Also updated HTMLaw library....
[yaffs-website] / web / core / modules / media / src / Entity / Media.php
1 <?php
2
3 namespace Drupal\media\Entity;
4
5 use Drupal\Core\Entity\EditorialContentEntityBase;
6 use Drupal\Core\Entity\EntityStorageInterface;
7 use Drupal\Core\Entity\EntityTypeInterface;
8 use Drupal\Core\Field\BaseFieldDefinition;
9 use Drupal\Core\StringTranslation\StringTranslationTrait;
10 use Drupal\media\MediaInterface;
11 use Drupal\media\MediaSourceEntityConstraintsInterface;
12 use Drupal\media\MediaSourceFieldConstraintsInterface;
13 use Drupal\user\UserInterface;
14
15 /**
16  * Defines the media entity class.
17  *
18  * @todo Remove default/fallback entity form operation when #2006348 is done.
19  * @see https://www.drupal.org/node/2006348.
20  *
21  * @ContentEntityType(
22  *   id = "media",
23  *   label = @Translation("Media"),
24  *   label_singular = @Translation("media item"),
25  *   label_plural = @Translation("media items"),
26  *   label_count = @PluralTranslation(
27  *     singular = "@count media item",
28  *     plural = "@count media items"
29  *   ),
30  *   bundle_label = @Translation("Media type"),
31  *   handlers = {
32  *     "storage" = "Drupal\media\MediaStorage",
33  *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
34  *     "list_builder" = "Drupal\media\MediaListBuilder",
35  *     "access" = "Drupal\media\MediaAccessControlHandler",
36  *     "form" = {
37  *       "default" = "Drupal\media\MediaForm",
38  *       "add" = "Drupal\media\MediaForm",
39  *       "edit" = "Drupal\media\MediaForm",
40  *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
41  *       "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm",
42  *     },
43  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
44  *     "views_data" = "Drupal\media\MediaViewsData",
45  *     "route_provider" = {
46  *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
47  *     }
48  *   },
49  *   base_table = "media",
50  *   data_table = "media_field_data",
51  *   revision_table = "media_revision",
52  *   revision_data_table = "media_field_revision",
53  *   translatable = TRUE,
54  *   show_revision_ui = TRUE,
55  *   entity_keys = {
56  *     "id" = "mid",
57  *     "revision" = "vid",
58  *     "bundle" = "bundle",
59  *     "label" = "name",
60  *     "langcode" = "langcode",
61  *     "uuid" = "uuid",
62  *     "published" = "status",
63  *   },
64  *   revision_metadata_keys = {
65  *     "revision_user" = "revision_user",
66  *     "revision_created" = "revision_created",
67  *     "revision_log_message" = "revision_log_message",
68  *   },
69  *   bundle_entity_type = "media_type",
70  *   permission_granularity = "bundle",
71  *   admin_permission = "administer media",
72  *   field_ui_base_route = "entity.media_type.edit_form",
73  *   common_reference_target = TRUE,
74  *   links = {
75  *     "add-page" = "/media/add",
76  *     "add-form" = "/media/add/{media_type}",
77  *     "canonical" = "/media/{media}",
78  *     "collection" = "/admin/content/media",
79  *     "delete-form" = "/media/{media}/delete",
80  *     "delete-multiple-form" = "/media/delete",
81  *     "edit-form" = "/media/{media}/edit",
82  *     "revision" = "/media/{media}/revisions/{media_revision}/view",
83  *   }
84  * )
85  */
86 class Media extends EditorialContentEntityBase implements MediaInterface {
87
88   use StringTranslationTrait;
89
90   /**
91    * {@inheritdoc}
92    */
93   public function getName() {
94     $name = $this->getEntityKey('label');
95
96     if (empty($name)) {
97       $media_source = $this->getSource();
98       return $media_source->getMetadata($this, $media_source->getPluginDefinition()['default_name_metadata_attribute']);
99     }
100
101     return $name;
102   }
103
104   /**
105    * {@inheritdoc}
106    */
107   public function label() {
108     return $this->getName();
109   }
110
111   /**
112    * {@inheritdoc}
113    */
114   public function setName($name) {
115     return $this->set('name', $name);
116   }
117
118   /**
119    * {@inheritdoc}
120    */
121   public function getCreatedTime() {
122     return $this->get('created')->value;
123   }
124
125   /**
126    * {@inheritdoc}
127    */
128   public function setCreatedTime($timestamp) {
129     return $this->set('created', $timestamp);
130   }
131
132   /**
133    * {@inheritdoc}
134    */
135   public function getOwner() {
136     return $this->get('uid')->entity;
137   }
138
139   /**
140    * {@inheritdoc}
141    */
142   public function setOwner(UserInterface $account) {
143     return $this->set('uid', $account->id());
144   }
145
146   /**
147    * {@inheritdoc}
148    */
149   public function getOwnerId() {
150     return $this->get('uid')->target_id;
151   }
152
153   /**
154    * {@inheritdoc}
155    */
156   public function setOwnerId($uid) {
157     return $this->set('uid', $uid);
158   }
159
160   /**
161    * {@inheritdoc}
162    */
163   public function getSource() {
164     return $this->bundle->entity->getSource();
165   }
166
167   /**
168    * Update the thumbnail for the media item.
169    *
170    * @param bool $from_queue
171    *   Specifies whether the thumbnail update is triggered from the queue.
172    *
173    * @return \Drupal\media\MediaInterface
174    *   The updated media item.
175    *
176    * @internal
177    *
178    * @todo There has been some disagreement about how to handle updates to
179    *   thumbnails. We need to decide on what the API will be for this.
180    *   https://www.drupal.org/node/2878119
181    */
182   protected function updateThumbnail($from_queue = FALSE) {
183     $this->thumbnail->target_id = $this->loadThumbnail($this->getThumbnailUri($from_queue))->id();
184
185     // Set the thumbnail alt.
186     $media_source = $this->getSource();
187     $plugin_definition = $media_source->getPluginDefinition();
188     if (!empty($plugin_definition['thumbnail_alt_metadata_attribute'])) {
189       $this->thumbnail->alt = $media_source->getMetadata($this, $plugin_definition['thumbnail_alt_metadata_attribute']);
190     }
191     else {
192       $this->thumbnail->alt = $this->t('Thumbnail', [], ['langcode' => $this->langcode->value]);
193     }
194
195     // Set the thumbnail title.
196     if (!empty($plugin_definition['thumbnail_title_metadata_attribute'])) {
197       $this->thumbnail->title = $media_source->getMetadata($this, $plugin_definition['thumbnail_title_metadata_attribute']);
198     }
199     else {
200       $this->thumbnail->title = $this->label();
201     }
202
203     return $this;
204   }
205
206   /**
207    * Loads the file entity for the thumbnail.
208    *
209    * If the file entity does not exist, it will be created.
210    *
211    * @param string $thumbnail_uri
212    *   (optional) The URI of the thumbnail, used to load or create the file
213    *   entity. If omitted, the default thumbnail URI will be used.
214    *
215    * @return \Drupal\file\FileInterface
216    *   The thumbnail file entity.
217    */
218   protected function loadThumbnail($thumbnail_uri = NULL) {
219     $values = [
220       'uri' => $thumbnail_uri ?: $this->getDefaultThumbnailUri(),
221     ];
222
223     $file_storage = $this->entityTypeManager()->getStorage('file');
224
225     $existing = $file_storage->loadByProperties($values);
226     if ($existing) {
227       $file = reset($existing);
228     }
229     else {
230       /** @var \Drupal\file\FileInterface $file */
231       $file = $file_storage->create($values);
232       if ($owner = $this->getOwner()) {
233         $file->setOwner($owner);
234       }
235       $file->setPermanent();
236       $file->save();
237     }
238     return $file;
239   }
240
241   /**
242    * Returns the URI of the default thumbnail.
243    *
244    * @return string
245    *   The default thumbnail URI.
246    */
247   protected function getDefaultThumbnailUri() {
248     $default_thumbnail_filename = $this->getSource()->getPluginDefinition()['default_thumbnail_filename'];
249     return \Drupal::config('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename;
250   }
251
252   /**
253    * Updates the queued thumbnail for the media item.
254    *
255    * @return \Drupal\media\MediaInterface
256    *   The updated media item.
257    *
258    * @internal
259    *
260    * @todo If the need arises in contrib, consider making this a public API,
261    *   by adding an interface that extends MediaInterface.
262    */
263   public function updateQueuedThumbnail() {
264     $this->updateThumbnail(TRUE);
265     return $this;
266   }
267
268   /**
269    * Gets the URI for the thumbnail of a media item.
270    *
271    * If thumbnail fetching is queued, new media items will use the default
272    * thumbnail, and existing media items will use the current thumbnail, until
273    * the queue is processed and the updated thumbnail has been fetched.
274    * Otherwise, the new thumbnail will be fetched immediately.
275    *
276    * @param bool $from_queue
277    *   Specifies whether the thumbnail is being fetched from the queue.
278    *
279    * @return string
280    *   The file URI for the thumbnail of the media item.
281    *
282    * @internal
283    */
284   protected function getThumbnailUri($from_queue) {
285     $thumbnails_queued = $this->bundle->entity->thumbnailDownloadsAreQueued();
286     if ($thumbnails_queued && $this->isNew()) {
287       return $this->getDefaultThumbnailUri();
288     }
289     elseif ($thumbnails_queued && !$from_queue) {
290       return $this->get('thumbnail')->entity->getFileUri();
291     }
292
293     $source = $this->getSource();
294     return $source->getMetadata($this, $source->getPluginDefinition()['thumbnail_uri_metadata_attribute']);
295   }
296
297   /**
298    * Determines if the source field value has changed.
299    *
300    * @return bool
301    *   TRUE if the source field value changed, FALSE otherwise.
302    *
303    * @internal
304    */
305   protected function hasSourceFieldChanged() {
306     $source_field_name = $this->getSource()->getConfiguration()['source_field'];
307     $current_items = $this->get($source_field_name);
308     return isset($this->original) && !$current_items->equals($this->original->get($source_field_name));
309   }
310
311   /**
312    * Determines if the thumbnail should be updated for a media item.
313    *
314    * @param bool $is_new
315    *   Specifies whether the media item is new.
316    *
317    * @return bool
318    *   TRUE if the thumbnail should be updated, FALSE otherwise.
319    */
320   protected function shouldUpdateThumbnail($is_new = FALSE) {
321     // Update thumbnail if we don't have a thumbnail yet or when the source
322     // field value changes.
323     return !$this->get('thumbnail')->entity || $is_new || $this->hasSourceFieldChanged();
324   }
325
326   /**
327    * {@inheritdoc}
328    */
329   public function preSave(EntityStorageInterface $storage) {
330     parent::preSave($storage);
331
332     // If no thumbnail has been explicitly set, use the default thumbnail.
333     if ($this->get('thumbnail')->isEmpty()) {
334       $this->thumbnail->target_id = $this->loadThumbnail()->id();
335     }
336   }
337
338   /**
339    * {@inheritdoc}
340    */
341   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
342     parent::postSave($storage, $update);
343     $is_new = !$update;
344     foreach ($this->translations as $langcode => $data) {
345       if ($this->hasTranslation($langcode)) {
346         $translation = $this->getTranslation($langcode);
347         if ($translation->bundle->entity->thumbnailDownloadsAreQueued() && $translation->shouldUpdateThumbnail($is_new)) {
348           \Drupal::queue('media_entity_thumbnail')->createItem(['id' => $translation->id()]);
349         }
350       }
351     }
352   }
353
354   /**
355    * {@inheritdoc}
356    */
357   public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
358     parent::preSaveRevision($storage, $record);
359
360     $is_new_revision = $this->isNewRevision();
361     if (!$is_new_revision && isset($this->original) && empty($record->revision_log_message)) {
362       // If we are updating an existing media item without adding a
363       // new revision, we need to make sure $entity->revision_log_message is
364       // reset whenever it is empty.
365       // Therefore, this code allows us to avoid clobbering an existing log
366       // entry with an empty one.
367       $record->revision_log_message = $this->original->revision_log_message->value;
368     }
369
370     if ($is_new_revision) {
371       $record->revision_created = self::getRequestTime();
372     }
373   }
374
375   /**
376    * Sets the media entity's field values from the source's metadata.
377    *
378    * Fetching the metadata could be slow (e.g., if requesting it from a remote
379    * API), so this is called by \Drupal\media\MediaStorage::save() prior to it
380    * beginning the database transaction, whereas static::preSave() executes
381    * after the transaction has already started.
382    *
383    * @internal
384    *   Expose this as an API in
385    *   https://www.drupal.org/project/drupal/issues/2992426.
386    */
387   public function prepareSave() {
388     // @todo If the source plugin talks to a remote API (e.g. oEmbed), this code
389     // might be performing a fair number of HTTP requests. This is dangerously
390     // brittle and should probably be handled by a queue, to avoid doing HTTP
391     // operations during entity save. See
392     // https://www.drupal.org/project/drupal/issues/2976875 for more.
393
394     // In order for metadata to be mapped correctly, $this->original must be
395     // set. However, that is only set once parent::save() is called, so work
396     // around that by setting it here.
397     if (!isset($this->original) && $id = $this->id()) {
398       $this->original = $this->entityTypeManager()
399         ->getStorage('media')
400         ->loadUnchanged($id);
401     }
402
403     $media_source = $this->getSource();
404     foreach ($this->translations as $langcode => $data) {
405       if ($this->hasTranslation($langcode)) {
406         $translation = $this->getTranslation($langcode);
407         // Try to set fields provided by the media source and mapped in
408         // media type config.
409         foreach ($translation->bundle->entity->getFieldMap() as $metadata_attribute_name => $entity_field_name) {
410           // Only save value in entity field if empty. Do not overwrite existing
411           // data.
412           if ($translation->hasField($entity_field_name) && ($translation->get($entity_field_name)->isEmpty() || $translation->hasSourceFieldChanged())) {
413             $translation->set($entity_field_name, $media_source->getMetadata($translation, $metadata_attribute_name));
414           }
415         }
416
417         // Try to set a default name for this media item if no name is provided.
418         if ($translation->get('name')->isEmpty()) {
419           $translation->setName($translation->getName());
420         }
421
422         // Set thumbnail.
423         if ($translation->shouldUpdateThumbnail($this->isNew())) {
424           $translation->updateThumbnail();
425         }
426       }
427     }
428   }
429
430   /**
431    * {@inheritdoc}
432    */
433   public function validate() {
434     $media_source = $this->getSource();
435
436     if ($media_source instanceof MediaSourceEntityConstraintsInterface) {
437       $entity_constraints = $media_source->getEntityConstraints();
438       $this->getTypedData()->getDataDefinition()->setConstraints($entity_constraints);
439     }
440
441     if ($media_source instanceof MediaSourceFieldConstraintsInterface) {
442       $source_field_name = $media_source->getConfiguration()['source_field'];
443       $source_field_constraints = $media_source->getSourceFieldConstraints();
444       $this->get($source_field_name)->getDataDefinition()->setConstraints($source_field_constraints);
445     }
446
447     return parent::validate();
448   }
449
450   /**
451    * {@inheritdoc}
452    */
453   public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
454     $fields = parent::baseFieldDefinitions($entity_type);
455
456     $fields['name'] = BaseFieldDefinition::create('string')
457       ->setLabel(t('Name'))
458       ->setRequired(TRUE)
459       ->setTranslatable(TRUE)
460       ->setRevisionable(TRUE)
461       ->setDefaultValue('')
462       ->setSetting('max_length', 255)
463       ->setDisplayOptions('form', [
464         'type' => 'string_textfield',
465         'weight' => -5,
466       ])
467       ->setDisplayConfigurable('form', TRUE)
468       ->setDisplayConfigurable('view', TRUE);
469
470     $fields['thumbnail'] = BaseFieldDefinition::create('image')
471       ->setLabel(t('Thumbnail'))
472       ->setDescription(t('The thumbnail of the media item.'))
473       ->setRevisionable(TRUE)
474       ->setTranslatable(TRUE)
475       ->setDisplayOptions('view', [
476         'type' => 'image',
477         'weight' => 5,
478         'label' => 'hidden',
479         'settings' => [
480           'image_style' => 'thumbnail',
481         ],
482       ])
483       ->setDisplayConfigurable('view', TRUE)
484       ->setReadOnly(TRUE);
485
486     $fields['uid'] = BaseFieldDefinition::create('entity_reference')
487       ->setLabel(t('Authored by'))
488       ->setDescription(t('The user ID of the author.'))
489       ->setRevisionable(TRUE)
490       ->setDefaultValueCallback(static::class . '::getCurrentUserId')
491       ->setSetting('target_type', 'user')
492       ->setTranslatable(TRUE)
493       ->setDisplayOptions('form', [
494         'type' => 'entity_reference_autocomplete',
495         'weight' => 5,
496         'settings' => [
497           'match_operator' => 'CONTAINS',
498           'size' => '60',
499           'autocomplete_type' => 'tags',
500           'placeholder' => '',
501         ],
502       ])
503       ->setDisplayConfigurable('form', TRUE)
504       ->setDisplayOptions('view', [
505         'label' => 'hidden',
506         'type' => 'author',
507         'weight' => 0,
508       ])
509       ->setDisplayConfigurable('view', TRUE);
510
511     $fields['status']
512       ->setDisplayOptions('form', [
513         'type' => 'boolean_checkbox',
514         'settings' => [
515           'display_label' => TRUE,
516         ],
517         'weight' => 100,
518       ])
519       ->setDisplayConfigurable('form', TRUE);
520
521     $fields['created'] = BaseFieldDefinition::create('created')
522       ->setLabel(t('Authored on'))
523       ->setDescription(t('The time the media item was created.'))
524       ->setTranslatable(TRUE)
525       ->setRevisionable(TRUE)
526       ->setDefaultValueCallback(static::class . '::getRequestTime')
527       ->setDisplayOptions('form', [
528         'type' => 'datetime_timestamp',
529         'weight' => 10,
530       ])
531       ->setDisplayConfigurable('form', TRUE)
532       ->setDisplayOptions('view', [
533         'label' => 'hidden',
534         'type' => 'timestamp',
535         'weight' => 0,
536       ])
537       ->setDisplayConfigurable('view', TRUE);
538
539     $fields['changed'] = BaseFieldDefinition::create('changed')
540       ->setLabel(t('Changed'))
541       ->setDescription(t('The time the media item was last edited.'))
542       ->setTranslatable(TRUE)
543       ->setRevisionable(TRUE);
544
545     return $fields;
546   }
547
548   /**
549    * Default value callback for 'uid' base field definition.
550    *
551    * @see ::baseFieldDefinitions()
552    *
553    * @return int[]
554    *   An array of default values.
555    */
556   public static function getCurrentUserId() {
557     return [\Drupal::currentUser()->id()];
558   }
559
560   /**
561    * {@inheritdoc}
562    */
563   public static function getRequestTime() {
564     return \Drupal::time()->getRequestTime();
565   }
566
567 }