Chris@0: getEntityKey('label'); Chris@0: Chris@17: if (empty($name)) { Chris@0: $media_source = $this->getSource(); Chris@0: return $media_source->getMetadata($this, $media_source->getPluginDefinition()['default_name_metadata_attribute']); Chris@0: } Chris@17: Chris@17: return $name; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function label() { Chris@0: return $this->getName(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setName($name) { Chris@0: return $this->set('name', $name); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCreatedTime() { Chris@0: return $this->get('created')->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setCreatedTime($timestamp) { Chris@0: return $this->set('created', $timestamp); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getSource() { Chris@0: return $this->bundle->entity->getSource(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Update the thumbnail for the media item. Chris@0: * Chris@0: * @param bool $from_queue Chris@0: * Specifies whether the thumbnail update is triggered from the queue. Chris@0: * Chris@0: * @return \Drupal\media\MediaInterface Chris@0: * The updated media item. Chris@0: * Chris@0: * @internal Chris@0: * Chris@0: * @todo There has been some disagreement about how to handle updates to Chris@0: * thumbnails. We need to decide on what the API will be for this. Chris@0: * https://www.drupal.org/node/2878119 Chris@0: */ Chris@0: protected function updateThumbnail($from_queue = FALSE) { Chris@17: $this->thumbnail->target_id = $this->loadThumbnail($this->getThumbnailUri($from_queue))->id(); Chris@0: Chris@17: // Set the thumbnail alt. Chris@17: $media_source = $this->getSource(); Chris@17: $plugin_definition = $media_source->getPluginDefinition(); Chris@17: Chris@17: $this->thumbnail->alt = ''; Chris@17: if (!empty($plugin_definition['thumbnail_alt_metadata_attribute'])) { Chris@17: $this->thumbnail->alt = $media_source->getMetadata($this, $plugin_definition['thumbnail_alt_metadata_attribute']); Chris@17: } Chris@17: Chris@17: return $this; Chris@17: } Chris@17: Chris@17: /** Chris@17: * Loads the file entity for the thumbnail. Chris@17: * Chris@17: * If the file entity does not exist, it will be created. Chris@17: * Chris@17: * @param string $thumbnail_uri Chris@17: * (optional) The URI of the thumbnail, used to load or create the file Chris@17: * entity. If omitted, the default thumbnail URI will be used. Chris@17: * Chris@17: * @return \Drupal\file\FileInterface Chris@17: * The thumbnail file entity. Chris@17: */ Chris@17: protected function loadThumbnail($thumbnail_uri = NULL) { Chris@17: $values = [ Chris@17: 'uri' => $thumbnail_uri ?: $this->getDefaultThumbnailUri(), Chris@17: ]; Chris@17: Chris@17: $file_storage = $this->entityTypeManager()->getStorage('file'); Chris@17: Chris@17: $existing = $file_storage->loadByProperties($values); Chris@0: if ($existing) { Chris@17: $file = reset($existing); Chris@0: } Chris@0: else { Chris@0: /** @var \Drupal\file\FileInterface $file */ Chris@17: $file = $file_storage->create($values); Chris@0: if ($owner = $this->getOwner()) { Chris@0: $file->setOwner($owner); Chris@0: } Chris@0: $file->setPermanent(); Chris@0: $file->save(); Chris@0: } Chris@17: return $file; Chris@17: } Chris@0: Chris@17: /** Chris@17: * Returns the URI of the default thumbnail. Chris@17: * Chris@17: * @return string Chris@17: * The default thumbnail URI. Chris@17: */ Chris@17: protected function getDefaultThumbnailUri() { Chris@17: $default_thumbnail_filename = $this->getSource()->getPluginDefinition()['default_thumbnail_filename']; Chris@17: return \Drupal::config('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Updates the queued thumbnail for the media item. Chris@0: * Chris@0: * @return \Drupal\media\MediaInterface Chris@0: * The updated media item. Chris@0: * Chris@0: * @internal Chris@0: * Chris@0: * @todo If the need arises in contrib, consider making this a public API, Chris@0: * by adding an interface that extends MediaInterface. Chris@0: */ Chris@0: public function updateQueuedThumbnail() { Chris@0: $this->updateThumbnail(TRUE); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the URI for the thumbnail of a media item. Chris@0: * Chris@0: * If thumbnail fetching is queued, new media items will use the default Chris@0: * thumbnail, and existing media items will use the current thumbnail, until Chris@0: * the queue is processed and the updated thumbnail has been fetched. Chris@0: * Otherwise, the new thumbnail will be fetched immediately. Chris@0: * Chris@0: * @param bool $from_queue Chris@0: * Specifies whether the thumbnail is being fetched from the queue. Chris@0: * Chris@0: * @return string Chris@0: * The file URI for the thumbnail of the media item. Chris@0: * Chris@0: * @internal Chris@0: */ Chris@0: protected function getThumbnailUri($from_queue) { Chris@0: $thumbnails_queued = $this->bundle->entity->thumbnailDownloadsAreQueued(); Chris@0: if ($thumbnails_queued && $this->isNew()) { Chris@17: return $this->getDefaultThumbnailUri(); Chris@0: } Chris@0: elseif ($thumbnails_queued && !$from_queue) { Chris@17: return $this->get('thumbnail')->entity->getFileUri(); Chris@0: } Chris@0: Chris@17: $source = $this->getSource(); Chris@17: return $source->getMetadata($this, $source->getPluginDefinition()['thumbnail_uri_metadata_attribute']); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determines if the source field value has changed. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the source field value changed, FALSE otherwise. Chris@0: * Chris@0: * @internal Chris@0: */ Chris@0: protected function hasSourceFieldChanged() { Chris@0: $source_field_name = $this->getSource()->getConfiguration()['source_field']; Chris@0: $current_items = $this->get($source_field_name); Chris@0: return isset($this->original) && !$current_items->equals($this->original->get($source_field_name)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determines if the thumbnail should be updated for a media item. Chris@0: * Chris@0: * @param bool $is_new Chris@0: * Specifies whether the media item is new. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the thumbnail should be updated, FALSE otherwise. Chris@0: */ Chris@0: protected function shouldUpdateThumbnail($is_new = FALSE) { Chris@0: // Update thumbnail if we don't have a thumbnail yet or when the source Chris@0: // field value changes. Chris@0: return !$this->get('thumbnail')->entity || $is_new || $this->hasSourceFieldChanged(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function preSave(EntityStorageInterface $storage) { Chris@0: parent::preSave($storage); Chris@0: Chris@17: // If no thumbnail has been explicitly set, use the default thumbnail. Chris@17: if ($this->get('thumbnail')->isEmpty()) { Chris@17: $this->thumbnail->target_id = $this->loadThumbnail()->id(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function postSave(EntityStorageInterface $storage, $update = TRUE) { Chris@0: parent::postSave($storage, $update); Chris@0: $is_new = !$update; Chris@0: foreach ($this->translations as $langcode => $data) { Chris@0: if ($this->hasTranslation($langcode)) { Chris@0: $translation = $this->getTranslation($langcode); Chris@0: if ($translation->bundle->entity->thumbnailDownloadsAreQueued() && $translation->shouldUpdateThumbnail($is_new)) { Chris@0: \Drupal::queue('media_entity_thumbnail')->createItem(['id' => $translation->id()]); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) { Chris@0: parent::preSaveRevision($storage, $record); Chris@0: Chris@0: $is_new_revision = $this->isNewRevision(); Chris@0: if (!$is_new_revision && isset($this->original) && empty($record->revision_log_message)) { Chris@0: // If we are updating an existing media item without adding a Chris@0: // new revision, we need to make sure $entity->revision_log_message is Chris@0: // reset whenever it is empty. Chris@0: // Therefore, this code allows us to avoid clobbering an existing log Chris@0: // entry with an empty one. Chris@0: $record->revision_log_message = $this->original->revision_log_message->value; Chris@0: } Chris@0: Chris@0: if ($is_new_revision) { Chris@0: $record->revision_created = self::getRequestTime(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@17: * Sets the media entity's field values from the source's metadata. Chris@17: * Chris@17: * Fetching the metadata could be slow (e.g., if requesting it from a remote Chris@17: * API), so this is called by \Drupal\media\MediaStorage::save() prior to it Chris@17: * beginning the database transaction, whereas static::preSave() executes Chris@17: * after the transaction has already started. Chris@17: * Chris@17: * @internal Chris@17: * Expose this as an API in Chris@17: * https://www.drupal.org/project/drupal/issues/2992426. Chris@17: */ Chris@17: public function prepareSave() { Chris@17: // @todo If the source plugin talks to a remote API (e.g. oEmbed), this code Chris@17: // might be performing a fair number of HTTP requests. This is dangerously Chris@17: // brittle and should probably be handled by a queue, to avoid doing HTTP Chris@17: // operations during entity save. See Chris@17: // https://www.drupal.org/project/drupal/issues/2976875 for more. Chris@17: Chris@17: // In order for metadata to be mapped correctly, $this->original must be Chris@17: // set. However, that is only set once parent::save() is called, so work Chris@17: // around that by setting it here. Chris@17: if (!isset($this->original) && $id = $this->id()) { Chris@17: $this->original = $this->entityTypeManager() Chris@17: ->getStorage('media') Chris@17: ->loadUnchanged($id); Chris@17: } Chris@17: Chris@17: $media_source = $this->getSource(); Chris@17: foreach ($this->translations as $langcode => $data) { Chris@17: if ($this->hasTranslation($langcode)) { Chris@17: $translation = $this->getTranslation($langcode); Chris@17: // Try to set fields provided by the media source and mapped in Chris@17: // media type config. Chris@17: foreach ($translation->bundle->entity->getFieldMap() as $metadata_attribute_name => $entity_field_name) { Chris@17: // Only save value in entity field if empty. Do not overwrite existing Chris@17: // data. Chris@17: if ($translation->hasField($entity_field_name) && ($translation->get($entity_field_name)->isEmpty() || $translation->hasSourceFieldChanged())) { Chris@17: $translation->set($entity_field_name, $media_source->getMetadata($translation, $metadata_attribute_name)); Chris@17: } Chris@17: } Chris@17: Chris@17: // Try to set a default name for this media item if no name is provided. Chris@17: if ($translation->get('name')->isEmpty()) { Chris@17: $translation->setName($translation->getName()); Chris@17: } Chris@17: Chris@17: // Set thumbnail. Chris@17: if ($translation->shouldUpdateThumbnail($this->isNew())) { Chris@17: $translation->updateThumbnail(); Chris@17: } Chris@17: } Chris@17: } Chris@17: } Chris@17: Chris@17: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function validate() { Chris@0: $media_source = $this->getSource(); Chris@0: Chris@0: if ($media_source instanceof MediaSourceEntityConstraintsInterface) { Chris@0: $entity_constraints = $media_source->getEntityConstraints(); Chris@0: $this->getTypedData()->getDataDefinition()->setConstraints($entity_constraints); Chris@0: } Chris@0: Chris@0: if ($media_source instanceof MediaSourceFieldConstraintsInterface) { Chris@0: $source_field_name = $media_source->getConfiguration()['source_field']; Chris@0: $source_field_constraints = $media_source->getSourceFieldConstraints(); Chris@0: $this->get($source_field_name)->getDataDefinition()->setConstraints($source_field_constraints); Chris@0: } Chris@0: Chris@0: return parent::validate(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { Chris@0: $fields = parent::baseFieldDefinitions($entity_type); Chris@18: $fields += static::ownerBaseFieldDefinitions($entity_type); Chris@0: Chris@0: $fields['name'] = BaseFieldDefinition::create('string') Chris@0: ->setLabel(t('Name')) Chris@0: ->setRequired(TRUE) Chris@0: ->setTranslatable(TRUE) Chris@0: ->setRevisionable(TRUE) Chris@0: ->setDefaultValue('') Chris@0: ->setSetting('max_length', 255) Chris@0: ->setDisplayOptions('form', [ Chris@0: 'type' => 'string_textfield', Chris@0: 'weight' => -5, Chris@0: ]) Chris@0: ->setDisplayConfigurable('form', TRUE) Chris@14: ->setDisplayConfigurable('view', TRUE); Chris@0: Chris@0: $fields['thumbnail'] = BaseFieldDefinition::create('image') Chris@0: ->setLabel(t('Thumbnail')) Chris@0: ->setDescription(t('The thumbnail of the media item.')) Chris@0: ->setRevisionable(TRUE) Chris@0: ->setTranslatable(TRUE) Chris@0: ->setDisplayOptions('view', [ Chris@0: 'type' => 'image', Chris@0: 'weight' => 5, Chris@0: 'label' => 'hidden', Chris@0: 'settings' => [ Chris@0: 'image_style' => 'thumbnail', Chris@0: ], Chris@0: ]) Chris@0: ->setDisplayConfigurable('view', TRUE) Chris@0: ->setReadOnly(TRUE); Chris@0: Chris@18: $fields['uid'] Chris@0: ->setLabel(t('Authored by')) Chris@0: ->setDescription(t('The user ID of the author.')) Chris@0: ->setRevisionable(TRUE) Chris@0: ->setDisplayOptions('form', [ Chris@0: 'type' => 'entity_reference_autocomplete', Chris@0: 'weight' => 5, Chris@0: 'settings' => [ Chris@0: 'match_operator' => 'CONTAINS', Chris@0: 'size' => '60', Chris@0: 'autocomplete_type' => 'tags', Chris@0: 'placeholder' => '', Chris@0: ], Chris@0: ]) Chris@0: ->setDisplayConfigurable('form', TRUE) Chris@0: ->setDisplayOptions('view', [ Chris@0: 'label' => 'hidden', Chris@0: 'type' => 'author', Chris@0: 'weight' => 0, Chris@0: ]) Chris@0: ->setDisplayConfigurable('view', TRUE); Chris@0: Chris@0: $fields['status'] Chris@0: ->setDisplayOptions('form', [ Chris@0: 'type' => 'boolean_checkbox', Chris@0: 'settings' => [ Chris@0: 'display_label' => TRUE, Chris@0: ], Chris@0: 'weight' => 100, Chris@0: ]) Chris@0: ->setDisplayConfigurable('form', TRUE); Chris@0: Chris@0: $fields['created'] = BaseFieldDefinition::create('created') Chris@0: ->setLabel(t('Authored on')) Chris@0: ->setDescription(t('The time the media item was created.')) Chris@0: ->setTranslatable(TRUE) Chris@0: ->setRevisionable(TRUE) Chris@0: ->setDefaultValueCallback(static::class . '::getRequestTime') Chris@0: ->setDisplayOptions('form', [ Chris@0: 'type' => 'datetime_timestamp', Chris@0: 'weight' => 10, Chris@0: ]) Chris@0: ->setDisplayConfigurable('form', TRUE) Chris@0: ->setDisplayOptions('view', [ Chris@0: 'label' => 'hidden', Chris@0: 'type' => 'timestamp', Chris@0: 'weight' => 0, Chris@0: ]) Chris@0: ->setDisplayConfigurable('view', TRUE); Chris@0: Chris@0: $fields['changed'] = BaseFieldDefinition::create('changed') Chris@0: ->setLabel(t('Changed')) Chris@0: ->setDescription(t('The time the media item was last edited.')) Chris@0: ->setTranslatable(TRUE) Chris@0: ->setRevisionable(TRUE); Chris@0: Chris@0: return $fields; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Default value callback for 'uid' base field definition. Chris@0: * Chris@0: * @see ::baseFieldDefinitions() Chris@0: * Chris@18: * @deprecated The ::getCurrentUserId method is deprecated in 8.6.x and will Chris@18: * be removed before 9.0.0. Chris@18: * Chris@0: * @return int[] Chris@0: * An array of default values. Chris@0: */ Chris@0: public static function getCurrentUserId() { Chris@18: @trigger_error('The ::getCurrentUserId method is deprecated in 8.6.x and will be removed before 9.0.0.', E_USER_DEPRECATED); Chris@0: return [\Drupal::currentUser()->id()]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function getRequestTime() { Chris@0: return \Drupal::time()->getRequestTime(); Chris@0: } Chris@0: Chris@0: }