acquia_dam-1.0.0-rc1/src/Plugin/media/Source/Asset.php
src/Plugin/media/Source/Asset.php
<?php declare(strict_types=1); namespace Drupal\acquia_dam\Plugin\media\Source; use Drupal\acquia_dam\Entity\MediaSourceField; use Drupal\acquia_dam\Plugin\Field\FieldType\AssetItem; use Drupal\Component\Utility\Unicode; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\media\MediaInterface; use Drupal\media\MediaSourceBase; use Drupal\media\MediaTypeInterface; use GuzzleHttp\Exception\TransferException; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Media source for DAM assets. * * @MediaSource( * id = "acquia_dam_asset", * label = @Translation("Asset"), * description = @Translation("Use an asset from the Acquia DAM"), * allowed_field_types = {"acquia_dam_asset"}, * default_thumbnail_filename = "no-thumbnail.png", * deriver = "Drupal\acquia_dam\Plugin\media\Source\AssetDeriver", * asset_search_key = "", * asset_search_value = "", * ) */ final class Asset extends MediaSourceBase { /** * Asset storage. * * @var array */ protected $assetData = []; /** * The DAM client factory. * * @var \Drupal\acquia_dam\Client\AcquiaDamClientFactory */ private $clientFactory; /** * The HTTP client. * * @var \GuzzleHttp\Client */ private $httpClient; /** * The file system. * * @var \Drupal\Core\File\FileSystemInterface */ private $fileSystem; /** * The token replacement service. * * @var \Drupal\Core\Utility\Token */ private $token; /** * Logger channel interface. * * @var \Drupal\Core\Logger\LoggerChannelInterface */ private $damLoggerChannel; /** * System date config. * * @var \Drupal\Core\Config\ImmutableConfig */ protected $config; /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->clientFactory = $container->get('acquia_dam.client.factory'); $instance->httpClient = $container->get('http_client'); $instance->fileSystem = $container->get('file_system'); $instance->token = $container->get('token'); $instance->damLoggerChannel = $container->get('logger.channel.acquia_dam'); $instance->config = $container->get('config.factory')->get('system.date'); return $instance; } /** * {@inheritdoc} */ public function getMetadataAttributes(): array { $attributes = [ 'created_date' => $this->t('Created date'), 'filename' => $this->t('File name'), 'size' => $this->t('Size'), 'last_update_date' => $this->t('Last updated date'), 'file_upload_date' => $this->t('File upload date'), 'expiration_date' => $this->t('Expiration date'), 'release_date' => $this->t('Release date'), 'deleted_date' => $this->t('Deleted date'), 'format_type' => $this->t('Format Type'), 'format' => $this->t('Format'), ]; if ($this->configFactory->get('acquia_dam.settings')->get('allowed_metadata')) { return array_merge($attributes, $this->configFactory->get('acquia_dam.settings')->get('allowed_metadata')); } return $attributes; } /** * {@inheritdoc} * * Disable PHPMD.CyclomaticComplexity due to the switch statement, which is * a pattern used in all the implementations of this method. * * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getMetadata(MediaInterface $media, $attribute_name) { [$asset_id, $version_id, $external_id] = array_values($this->getSourceFieldValue($media)); if (empty($asset_id)) { return NULL; } if ($version_id === NULL) { $version_id = ''; } if ($external_id === NULL) { $external_id = ''; } $asset = $this->assetData; if ($asset === []) { try { $asset = $this->clientFactory->getSiteClient()->getAsset($asset_id, $version_id); } catch (\Exception $exception) { $this->damLoggerChannel->error(sprintf( 'Following error occurred while trying to get asset from dam. Asset: %s, error: %s', $asset_id, $exception->getMessage() ) ); return NULL; } } // The field mapping is used by some attributes to transform values for // better storage compatibility. $field_map = $media->bundle->entity->getFieldMap(); $field_definition = NULL; if (isset($field_map[$attribute_name])) { $field_definition = $media->getFieldDefinition($field_map[$attribute_name]); } switch ($attribute_name) { case 'filename': case 'default_name': return $asset['filename']; case 'id': case 'version_id': return $asset['id']; case 'size': return $asset['file_properties']['size_in_kbytes'] * 1024; case 'thumbnail_uri': return $this->getLocalThumbnailUri($asset_id, $version_id, $asset); case 'embeds': return $asset['embeds']; case 'external_id': return $asset['external_id']; case 'thumbnails': return $asset['thumbnails']; case 'image_properties': return $asset['file_properties']['image_properties']; case 'format_type': return $asset['file_properties']['format_type']; case 'format': return $asset['file_properties']['format']; case 'video_properties': return $asset['file_properties']['video_properties']; case 'created_date': case 'last_update_date': case 'file_upload_date': case 'deleted_date': return $asset[$attribute_name] ? $this->transformMetadataForStorage($asset[$attribute_name], 'datetime', $field_definition) : NULL; case 'expiration_date': case 'release_date': return $asset['security'][$attribute_name] ? $this->transformMetadataForStorage($asset['security'][$attribute_name], 'datetime', $field_definition) : NULL; default: if (!array_key_exists($attribute_name, $asset['metadata']['fields'])) { return NULL; } $value = $asset['metadata']['fields'][$attribute_name]; if (count($asset['metadata']['fields'][$attribute_name]) === 0) { return NULL; } $is_multiple = $field_definition && $field_definition->getFieldStorageDefinition()->isMultiple(); if (isset($asset['metadata_info']) && $field_definition !== NULL) { $metadata_type = self::getMetadataFieldType($asset['metadata_info'], $attribute_name); if ($metadata_type !== NULL) { $value = $this->transformMetadataForStorage($value, $metadata_type, $field_definition); } } return $is_multiple ? $value : implode(', ', $value); } } /** * Transforms metadata values for field storage. * * @param string|array $value * The metadata's value. * @param string $metadata_type * The metadata's type. * @param \Drupal\Core\Field\FieldDefinitionInterface|null $field_definition * The field definition, if metadata is mapped to a field. * * @return string|array * The transformed metadata values. */ private function transformMetadataForStorage($value, string $metadata_type, ?FieldDefinitionInterface $field_definition) { if ($field_definition === NULL) { return $value; } $field_type = $field_definition->getType(); $field_storage_definition = $field_definition->getFieldStorageDefinition(); if ($field_type === 'string') { $max_length = $field_definition->getSetting('max_length'); if (is_array($value)) { $value = array_map(function ($value) use ($max_length) { return $this->formatStringValues($value, $max_length); }, $value); } else { $value = $this->formatStringValues($value, $max_length); } } if (in_array($metadata_type, ['date', 'datetime'])) { $source_format = $metadata_type === 'date' ? 'Y-m-d' : \DateTimeInterface::ATOM; if ($field_type === 'datetime') { $datetime_type = $field_storage_definition->getSetting('datetime_type'); $format = $datetime_type === DateTimeItem::DATETIME_TYPE_DATETIME ? DateTimeItemInterface::DATETIME_STORAGE_FORMAT : DateTimeItemInterface::DATE_STORAGE_FORMAT; } elseif ($field_type === 'timestamp') { $format = 'U'; } else { return $value; } if (is_array($value)) { $value = array_map(function ($value) use ($source_format, $format) { return $this->formatDateForDateField($value, $source_format, $format); }, $value); } else { $value = $this->formatDateForDateField($value, $source_format, $format); } } return $value; } /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { $form = parent::buildConfigurationForm($form, $form_state); $form['source_field']['#default_value'] = MediaSourceField::SOURCE_FIELD_NAME; $form['source_field']['#disabled'] = TRUE; return $form; } /** * {@inheritdoc} */ public function getSourceFieldValue(MediaInterface $media): array { $items = $media->get($this->getSourceFieldName()); if ($items->isEmpty()) { return [ 'asset_id' => '', 'version_id' => '', 'external_id' => '', ]; } $field_item = $items->first(); assert($field_item instanceof AssetItem); // The ::getValue method on FieldItem only returns an array where properties // have been initiated with a value. It does not return properties that have // no value. Using ::toArray ensures the result has `version_id`, which may // be empty when a media item is first saved. return $field_item->toArray(); } /** * {@inheritdoc} */ public function createSourceField(MediaTypeInterface $type) { return $this->getSourceFieldDefinition($type); } /** * {@inheritdoc} */ public function getSourceFieldDefinition(MediaTypeInterface $type) { return MediaSourceField::getFieldDefinition('media', $type->id(), $type->label()); } /** * {@inheritdoc} */ protected function getSourceFieldName() { return MediaSourceField::SOURCE_FIELD_NAME; } /** * Returns the local URI for a resource thumbnail. * * @param string $asset_id * The asset ID. * @param string $version_id * The version ID, an empty string is allowed if version unknown. * @param array $asset * The asset. * * @return string * The local thumbnail URI, or NULL if it could not be downloaded. * * @throws \GuzzleHttp\Exception\GuzzleException * * @see \Drupal\media\Plugin\media\Source\OEmbed::getLocalThumbnailUri */ protected function getLocalThumbnailUri(string $asset_id, string $version_id, array $asset): string { if ($version_id === '') { // The version ID was not yet set. This happens when the media is saved // without a version ID and Media::prepareSave is invoked before preSave // where the version is populated. Give a generic version ID for the // filename of this thumbnail. $version_id = 'default'; } $directory = 'public://acquia_dam_thumbnails/' . $asset_id; if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) { return ''; } // If there is an existing local thumbnail for the version, return it. $files = $this->fileSystem->scanDirectory($directory, "/^$version_id\..*/"); if (count($files) > 0) { return reset($files)->uri; } $thumbnail_url = $asset['thumbnails']['160px']['url']; try { $response = $this->httpClient->request('GET', $thumbnail_url); if ($response->getStatusCode() === 200) { $local_thumbnail_uri = $directory . DIRECTORY_SEPARATOR . "$version_id.png"; $this->fileSystem->saveData((string) $response->getBody(), $local_thumbnail_uri, FileSystemInterface::EXISTS_REPLACE); return $local_thumbnail_uri; } } catch (TransferException $e) { $this->damLoggerChannel->error(sprintf( 'Unable to download thumbnail at %s: %s %s', $thumbnail_url, get_class($e), $e->getMessage() )); return ''; } catch (FileException $e) { $this->damLoggerChannel->error(sprintf( 'Unable to download thumbnail at %s: %s %s', $thumbnail_url, get_class($e), $e->getMessage() )); return ''; } return ''; } /** * Formats date coming from DAM to save into storage. * * @param string $value * Date string coming from API in ISO8601 format. * @param string $source_format * The source date time format. * @param string $format * The date time format. * * @return string * The formatted date. */ protected function formatDateForDateField(string $value, string $source_format, string $format): string { try { $date = DrupalDateTime::createFromFormat( $source_format, $value, new \DateTimeZone($this->config->get('timezone.default')), [ // We do not want to validate the format. Incoming ISO8601 has the Z // timezone offset, while PHP may return +00:00 when comparing the // output with the `P` option. 'validate_format' => FALSE, ] ); // If the format did not include an explicit time portion, then the time // will be set from the current time instead. Provide a default for // consistent values. if (!str_contains($value, 'T')) { $date->setDefaultDateTime(); } } catch (\InvalidArgumentException | \UnexpectedValueException $exception) { return $value; } return $date->format($format); } /** * Cuts last part of the string if it is longer than allowed. * * @param string $value * String value. * @param int $max_length * Allowed max length on field. * * @return string * Formatted string. */ protected function formatStringValues(string $value, int $max_length): string { if ($max_length < strlen($value)) { return Unicode::truncate($value, $max_length - 3, TRUE, TRUE); } return $value; } /** * Sets the asset data. * * @param array $data * The asset data. */ public function setAssetData(array $data) { $this->assetData = $data; } /** * Gets the field type for a metadata field. * * @param array $metadata_info * The asset's metadata info. * @param string $field_name * The metadata field name. * * @return string|null * The field type. */ private static function getMetadataFieldType(array $metadata_info, string $field_name): ?string { $mapping = []; foreach ($metadata_info['field_set_fields'] as $field) { $mapping[$field['key']] = $field['type']; } return $mapping[$field_name] ?? NULL; } }