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