Security update for Core, with self-updated composer
[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\Core\Entity\Sql\SqlContentEntityStorage",
33  *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
34  *     "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
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  *     },
42  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
43  *     "views_data" = "Drupal\media\MediaViewsData",
44  *     "route_provider" = {
45  *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
46  *     }
47  *   },
48  *   base_table = "media",
49  *   data_table = "media_field_data",
50  *   revision_table = "media_revision",
51  *   revision_data_table = "media_field_revision",
52  *   translatable = TRUE,
53  *   show_revision_ui = TRUE,
54  *   entity_keys = {
55  *     "id" = "mid",
56  *     "revision" = "vid",
57  *     "bundle" = "bundle",
58  *     "label" = "name",
59  *     "langcode" = "langcode",
60  *     "uuid" = "uuid",
61  *     "published" = "status",
62  *   },
63  *   revision_metadata_keys = {
64  *     "revision_user" = "revision_user",
65  *     "revision_created" = "revision_created",
66  *     "revision_log_message" = "revision_log_message",
67  *   },
68  *   bundle_entity_type = "media_type",
69  *   permission_granularity = "entity_type",
70  *   admin_permission = "administer media",
71  *   field_ui_base_route = "entity.media_type.edit_form",
72  *   common_reference_target = TRUE,
73  *   links = {
74  *     "add-page" = "/media/add",
75  *     "add-form" = "/media/add/{media_type}",
76  *     "canonical" = "/media/{media}",
77  *     "delete-form" = "/media/{media}/delete",
78  *     "edit-form" = "/media/{media}/edit",
79  *     "revision" = "/media/{media}/revisions/{media_revision}/view",
80  *   }
81  * )
82  */
83 class Media extends EditorialContentEntityBase implements MediaInterface {
84
85   use StringTranslationTrait;
86
87   /**
88    * {@inheritdoc}
89    */
90   public function getName() {
91     $name = $this->get('name');
92
93     if ($name->isEmpty()) {
94       $media_source = $this->getSource();
95       return $media_source->getMetadata($this, $media_source->getPluginDefinition()['default_name_metadata_attribute']);
96     }
97     else {
98       return $name->value;
99     }
100   }
101
102   /**
103    * {@inheritdoc}
104    */
105   public function label() {
106     return $this->getName();
107   }
108
109   /**
110    * {@inheritdoc}
111    */
112   public function setName($name) {
113     return $this->set('name', $name);
114   }
115
116   /**
117    * {@inheritdoc}
118    */
119   public function getCreatedTime() {
120     return $this->get('created')->value;
121   }
122
123   /**
124    * {@inheritdoc}
125    */
126   public function setCreatedTime($timestamp) {
127     return $this->set('created', $timestamp);
128   }
129
130   /**
131    * {@inheritdoc}
132    */
133   public function getOwner() {
134     return $this->get('uid')->entity;
135   }
136
137   /**
138    * {@inheritdoc}
139    */
140   public function setOwner(UserInterface $account) {
141     return $this->set('uid', $account->id());
142   }
143
144   /**
145    * {@inheritdoc}
146    */
147   public function getOwnerId() {
148     return $this->get('uid')->target_id;
149   }
150
151   /**
152    * {@inheritdoc}
153    */
154   public function setOwnerId($uid) {
155     return $this->set('uid', $uid);
156   }
157
158   /**
159    * {@inheritdoc}
160    */
161   public function getSource() {
162     return $this->bundle->entity->getSource();
163   }
164
165   /**
166    * Update the thumbnail for the media item.
167    *
168    * @param bool $from_queue
169    *   Specifies whether the thumbnail update is triggered from the queue.
170    *
171    * @return \Drupal\media\MediaInterface
172    *   The updated media item.
173    *
174    * @internal
175    *
176    * @todo There has been some disagreement about how to handle updates to
177    *   thumbnails. We need to decide on what the API will be for this.
178    *   https://www.drupal.org/node/2878119
179    */
180   protected function updateThumbnail($from_queue = FALSE) {
181     $file_storage = \Drupal::service('entity_type.manager')->getStorage('file');
182     $thumbnail_uri = $this->getThumbnailUri($from_queue);
183     $existing = $file_storage->getQuery()
184       ->condition('uri', $thumbnail_uri)
185       ->execute();
186
187     if ($existing) {
188       $this->thumbnail->target_id = reset($existing);
189     }
190     else {
191       /** @var \Drupal\file\FileInterface $file */
192       $file = $file_storage->create(['uri' => $thumbnail_uri]);
193       if ($owner = $this->getOwner()) {
194         $file->setOwner($owner);
195       }
196       $file->setPermanent();
197       $file->save();
198       $this->thumbnail->target_id = $file->id();
199     }
200
201     // Set the thumbnail alt.
202     $media_source = $this->getSource();
203     $plugin_definition = $media_source->getPluginDefinition();
204     if (!empty($plugin_definition['thumbnail_alt_metadata_attribute'])) {
205       $this->thumbnail->alt = $media_source->getMetadata($this, $plugin_definition['thumbnail_alt_metadata_attribute']);
206     }
207     else {
208       $this->thumbnail->alt = $this->t('Thumbnail', [], ['langcode' => $this->langcode->value]);
209     }
210
211     // Set the thumbnail title.
212     if (!empty($plugin_definition['thumbnail_title_metadata_attribute'])) {
213       $this->thumbnail->title = $media_source->getMetadata($this, $plugin_definition['thumbnail_title_metadata_attribute']);
214     }
215     else {
216       $this->thumbnail->title = $this->label();
217     }
218
219     return $this;
220   }
221
222   /**
223    * Updates the queued thumbnail for the media item.
224    *
225    * @return \Drupal\media\MediaInterface
226    *   The updated media item.
227    *
228    * @internal
229    *
230    * @todo If the need arises in contrib, consider making this a public API,
231    *   by adding an interface that extends MediaInterface.
232    */
233   public function updateQueuedThumbnail() {
234     $this->updateThumbnail(TRUE);
235     return $this;
236   }
237
238   /**
239    * Gets the URI for the thumbnail of a media item.
240    *
241    * If thumbnail fetching is queued, new media items will use the default
242    * thumbnail, and existing media items will use the current thumbnail, until
243    * the queue is processed and the updated thumbnail has been fetched.
244    * Otherwise, the new thumbnail will be fetched immediately.
245    *
246    * @param bool $from_queue
247    *   Specifies whether the thumbnail is being fetched from the queue.
248    *
249    * @return string
250    *   The file URI for the thumbnail of the media item.
251    *
252    * @internal
253    */
254   protected function getThumbnailUri($from_queue) {
255     $thumbnails_queued = $this->bundle->entity->thumbnailDownloadsAreQueued();
256     if ($thumbnails_queued && $this->isNew()) {
257       $default_thumbnail_filename = $this->getSource()->getPluginDefinition()['default_thumbnail_filename'];
258       $thumbnail_uri = \Drupal::service('config.factory')->get('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename;
259     }
260     elseif ($thumbnails_queued && !$from_queue) {
261       $thumbnail_uri = $this->get('thumbnail')->entity->getFileUri();
262     }
263     else {
264       $thumbnail_uri = $this->getSource()->getMetadata($this, $this->getSource()->getPluginDefinition()['thumbnail_uri_metadata_attribute']);
265     }
266
267     return $thumbnail_uri;
268   }
269
270   /**
271    * Determines if the source field value has changed.
272    *
273    * @return bool
274    *   TRUE if the source field value changed, FALSE otherwise.
275    *
276    * @internal
277    */
278   protected function hasSourceFieldChanged() {
279     $source_field_name = $this->getSource()->getConfiguration()['source_field'];
280     $current_items = $this->get($source_field_name);
281     return isset($this->original) && !$current_items->equals($this->original->get($source_field_name));
282   }
283
284   /**
285    * Determines if the thumbnail should be updated for a media item.
286    *
287    * @param bool $is_new
288    *   Specifies whether the media item is new.
289    *
290    * @return bool
291    *   TRUE if the thumbnail should be updated, FALSE otherwise.
292    */
293   protected function shouldUpdateThumbnail($is_new = FALSE) {
294     // Update thumbnail if we don't have a thumbnail yet or when the source
295     // field value changes.
296     return !$this->get('thumbnail')->entity || $is_new || $this->hasSourceFieldChanged();
297   }
298
299   /**
300    * {@inheritdoc}
301    */
302   public function preSave(EntityStorageInterface $storage) {
303     parent::preSave($storage);
304
305     $media_source = $this->getSource();
306     foreach ($this->translations as $langcode => $data) {
307       if ($this->hasTranslation($langcode)) {
308         $translation = $this->getTranslation($langcode);
309         // Try to set fields provided by the media source and mapped in
310         // media type config.
311         foreach ($translation->bundle->entity->getFieldMap() as $metadata_attribute_name => $entity_field_name) {
312           // Only save value in entity field if empty. Do not overwrite existing
313           // data.
314           if ($translation->hasField($entity_field_name) && ($translation->get($entity_field_name)->isEmpty() || $translation->hasSourceFieldChanged())) {
315             $translation->set($entity_field_name, $media_source->getMetadata($translation, $metadata_attribute_name));
316           }
317         }
318
319         // Try to set a default name for this media item if no name is provided.
320         if ($translation->get('name')->isEmpty()) {
321           $translation->setName($translation->getName());
322         }
323
324         // Set thumbnail.
325         if ($translation->shouldUpdateThumbnail()) {
326           $translation->updateThumbnail();
327         }
328       }
329     }
330   }
331
332   /**
333    * {@inheritdoc}
334    */
335   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
336     parent::postSave($storage, $update);
337     $is_new = !$update;
338     foreach ($this->translations as $langcode => $data) {
339       if ($this->hasTranslation($langcode)) {
340         $translation = $this->getTranslation($langcode);
341         if ($translation->bundle->entity->thumbnailDownloadsAreQueued() && $translation->shouldUpdateThumbnail($is_new)) {
342           \Drupal::queue('media_entity_thumbnail')->createItem(['id' => $translation->id()]);
343         }
344       }
345     }
346   }
347
348   /**
349    * {@inheritdoc}
350    */
351   public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
352     parent::preSaveRevision($storage, $record);
353
354     $is_new_revision = $this->isNewRevision();
355     if (!$is_new_revision && isset($this->original) && empty($record->revision_log_message)) {
356       // If we are updating an existing media item without adding a
357       // new revision, we need to make sure $entity->revision_log_message is
358       // reset whenever it is empty.
359       // Therefore, this code allows us to avoid clobbering an existing log
360       // entry with an empty one.
361       $record->revision_log_message = $this->original->revision_log_message->value;
362     }
363
364     if ($is_new_revision) {
365       $record->revision_created = self::getRequestTime();
366     }
367   }
368
369   /**
370    * {@inheritdoc}
371    */
372   public function validate() {
373     $media_source = $this->getSource();
374
375     if ($media_source instanceof MediaSourceEntityConstraintsInterface) {
376       $entity_constraints = $media_source->getEntityConstraints();
377       $this->getTypedData()->getDataDefinition()->setConstraints($entity_constraints);
378     }
379
380     if ($media_source instanceof MediaSourceFieldConstraintsInterface) {
381       $source_field_name = $media_source->getConfiguration()['source_field'];
382       $source_field_constraints = $media_source->getSourceFieldConstraints();
383       $this->get($source_field_name)->getDataDefinition()->setConstraints($source_field_constraints);
384     }
385
386     return parent::validate();
387   }
388
389   /**
390    * {@inheritdoc}
391    */
392   public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
393     $fields = parent::baseFieldDefinitions($entity_type);
394
395     $fields['name'] = BaseFieldDefinition::create('string')
396       ->setLabel(t('Name'))
397       ->setRequired(TRUE)
398       ->setTranslatable(TRUE)
399       ->setRevisionable(TRUE)
400       ->setDefaultValue('')
401       ->setSetting('max_length', 255)
402       ->setDisplayOptions('form', [
403         'type' => 'string_textfield',
404         'weight' => -5,
405       ])
406       ->setDisplayConfigurable('form', TRUE)
407       ->setDisplayConfigurable('view', TRUE)
408       ->setDisplayOptions('view', [
409         'label' => 'hidden',
410         'type' => 'string',
411         'weight' => -5,
412       ]);
413
414     $fields['thumbnail'] = BaseFieldDefinition::create('image')
415       ->setLabel(t('Thumbnail'))
416       ->setDescription(t('The thumbnail of the media item.'))
417       ->setRevisionable(TRUE)
418       ->setTranslatable(TRUE)
419       ->setDisplayOptions('view', [
420         'type' => 'image',
421         'weight' => 5,
422         'label' => 'hidden',
423         'settings' => [
424           'image_style' => 'thumbnail',
425         ],
426       ])
427       ->setDisplayConfigurable('view', TRUE)
428       ->setReadOnly(TRUE);
429
430     $fields['uid'] = BaseFieldDefinition::create('entity_reference')
431       ->setLabel(t('Authored by'))
432       ->setDescription(t('The user ID of the author.'))
433       ->setRevisionable(TRUE)
434       ->setDefaultValueCallback(static::class . '::getCurrentUserId')
435       ->setSetting('target_type', 'user')
436       ->setTranslatable(TRUE)
437       ->setDisplayOptions('form', [
438         'type' => 'entity_reference_autocomplete',
439         'weight' => 5,
440         'settings' => [
441           'match_operator' => 'CONTAINS',
442           'size' => '60',
443           'autocomplete_type' => 'tags',
444           'placeholder' => '',
445         ],
446       ])
447       ->setDisplayConfigurable('form', TRUE)
448       ->setDisplayOptions('view', [
449         'label' => 'hidden',
450         'type' => 'author',
451         'weight' => 0,
452       ])
453       ->setDisplayConfigurable('view', TRUE);
454
455     $fields['status']
456       ->setDisplayOptions('form', [
457         'type' => 'boolean_checkbox',
458         'settings' => [
459           'display_label' => TRUE,
460         ],
461         'weight' => 100,
462       ])
463       ->setDisplayConfigurable('form', TRUE);
464
465     $fields['created'] = BaseFieldDefinition::create('created')
466       ->setLabel(t('Authored on'))
467       ->setDescription(t('The time the media item was created.'))
468       ->setTranslatable(TRUE)
469       ->setRevisionable(TRUE)
470       ->setDefaultValueCallback(static::class . '::getRequestTime')
471       ->setDisplayOptions('form', [
472         'type' => 'datetime_timestamp',
473         'weight' => 10,
474       ])
475       ->setDisplayConfigurable('form', TRUE)
476       ->setDisplayOptions('view', [
477         'label' => 'hidden',
478         'type' => 'timestamp',
479         'weight' => 0,
480       ])
481       ->setDisplayConfigurable('view', TRUE);
482
483     $fields['changed'] = BaseFieldDefinition::create('changed')
484       ->setLabel(t('Changed'))
485       ->setDescription(t('The time the media item was last edited.'))
486       ->setTranslatable(TRUE)
487       ->setRevisionable(TRUE);
488
489     return $fields;
490   }
491
492   /**
493    * Default value callback for 'uid' base field definition.
494    *
495    * @see ::baseFieldDefinitions()
496    *
497    * @return int[]
498    *   An array of default values.
499    */
500   public static function getCurrentUserId() {
501     return [\Drupal::currentUser()->id()];
502   }
503
504   /**
505    * {@inheritdoc}
506    */
507   public static function getRequestTime() {
508     return \Drupal::time()->getRequestTime();
509   }
510
511 }