getOwner(); $data = [ 'uuid' => $media->uuid(), 'mid' => (int) $media->id(), 'type' => $media->bundle(), 'name' => $media->getName(), 'created' => $this->formatTimestamp((int) $media->getCreatedTime()), 'changed' => $this->formatTimestamp((int) $media->getChangedTime()), 'status' => $media->isPublished(), 'owner' => [ 'uid' => (int) $owner->id(), 'name' => $owner->getAccountName(), 'display_name' => $owner->getDisplayName(), ], 'source' => $this->serializeMediaSource($media), 'fields' => $this->serializeFields($media), '_metadata' => [ 'ocfl_version' => '1.1', 'export_timestamp' => $this->formatTimestamp($this->time->getRequestTime()), 'drupal_version' => \Drupal::VERSION, 'entity_type' => 'media', ], ]; return $data; } /** * Serialize the media source (primary file/data). * * @param \Drupal\media\MediaInterface $media * The media entity. * * @return array * The serialized source data. */ protected function serializeMediaSource(MediaInterface $media): array { $source = $media->getSource(); $sourceConfig = $source->getConfiguration(); $sourceField = $sourceConfig['source_field'] ?? NULL; $data = [ 'plugin_id' => $source->getPluginId(), ]; if ($sourceField && $media->hasField($sourceField)) { $sourceFieldItems = $media->get($sourceField); if (!$sourceFieldItems->isEmpty()) { $sourceItem = $sourceFieldItems->first(); $file = $sourceItem->entity ?? NULL; if ($file instanceof FileInterface) { $data['file'] = [ 'uuid' => $file->uuid(), 'filename' => $file->getFilename(), 'uri' => $file->getFileUri(), 'filemime' => $file->getMimeType(), 'filesize' => (int) $file->getSize(), 'url' => \Drupal::service('file_url_generator')->generateAbsoluteString($file->getFileUri()), ]; // Include alt and title if available (for images). if (isset($sourceItem->alt)) { $data['alt'] = $sourceItem->alt; } if (isset($sourceItem->title)) { $data['title'] = $sourceItem->title; } if (isset($sourceItem->width)) { $data['width'] = (int) $sourceItem->width; } if (isset($sourceItem->height)) { $data['height'] = (int) $sourceItem->height; } } else { // For non-file sources (e.g., oEmbed, remote video). $data['value'] = $sourceItem->getValue(); } } } return $data; } /** * Serialize all fields of a media entity. * * @param \Drupal\media\MediaInterface $media * The media entity to serialize fields from. * * @return array * The serialized field data. */ protected function serializeFields(MediaInterface $media): array { $fields = []; $fieldDefinitions = $media->getFieldDefinitions(); $sourceField = $media->getSource()->getConfiguration()['source_field'] ?? NULL; foreach ($fieldDefinitions as $fieldName => $definition) { // Skip base fields and source field (already serialized). if ($this->isBaseField($fieldName) || $fieldName === $sourceField) { continue; } $fieldItems = $media->get($fieldName); if ($fieldItems->isEmpty()) { continue; } $fields[$fieldName] = $this->serializeFieldItems($fieldItems); } return $fields; } /** * Check if a field is a base field that should be skipped. * * @param string $fieldName * The field name. * * @return bool * TRUE if this is a base field to skip. */ protected function isBaseField(string $fieldName): bool { $baseFields = [ 'mid', 'vid', 'uuid', 'langcode', 'bundle', 'revision_created', 'revision_user', 'revision_log_message', 'status', 'uid', 'name', 'thumbnail', 'created', 'changed', 'default_langcode', 'revision_default', 'revision_translation_affected', ]; return in_array($fieldName, $baseFields, TRUE); } /** * Serialize field items. * * @param \Drupal\Core\Field\FieldItemListInterface $fieldItems * The field items to serialize. * * @return mixed * The serialized field data. */ protected function serializeFieldItems(FieldItemListInterface $fieldItems): mixed { $fieldType = $fieldItems->getFieldDefinition()->getType(); $values = []; foreach ($fieldItems as $delta => $item) { $value = $this->serializeFieldItem($item, $fieldType); if ($value !== NULL) { $values[] = $value; } } // Return single value for single-value fields. if (count($values) === 1 && $fieldItems->getFieldDefinition()->getFieldStorageDefinition()->getCardinality() === 1) { return $values[0]; } return $values; } /** * Serialize a single field item. * * @param mixed $item * The field item. * @param string $fieldType * The field type. * * @return mixed * The serialized value. */ protected function serializeFieldItem(mixed $item, string $fieldType): mixed { return match ($fieldType) { 'entity_reference' => $this->serializeEntityReference($item), 'entity_reference_revisions' => $this->serializeEntityReferenceRevisions($item), 'text_with_summary' => [ 'value' => $item->value, 'format' => $item->format, 'summary' => $item->summary ?? '', ], 'text_long', 'text' => [ 'value' => $item->value, 'format' => $item->format ?? NULL, ], 'image' => [ 'target_id' => (int) $item->target_id, 'alt' => $item->alt ?? '', 'title' => $item->title ?? '', 'width' => $item->width ? (int) $item->width : NULL, 'height' => $item->height ? (int) $item->height : NULL, 'entity' => $this->serializeFileEntity($item->entity), ], 'file' => [ 'target_id' => (int) $item->target_id, 'description' => $item->description ?? '', 'display' => $item->display ?? TRUE, 'entity' => $this->serializeFileEntity($item->entity), ], 'link' => [ 'uri' => $item->uri, 'title' => $item->title ?? '', 'options' => $item->options ?? [], ], 'datetime' => $item->value, 'daterange' => [ 'value' => $item->value, 'end_value' => $item->end_value, ], 'boolean' => (bool) $item->value, 'integer', 'list_integer' => (int) $item->value, 'decimal', 'float', 'list_float' => (float) $item->value, 'email' => $item->value, 'telephone' => $item->value, default => $item->getValue(), }; } /** * Serialize an entity reference field item. * * @param mixed $item * The field item. * * @return array|null * The serialized reference or NULL. */ protected function serializeEntityReference(mixed $item): ?array { $entity = $item->entity; if (!$entity) { return NULL; } $data = [ 'target_id' => (int) $item->target_id, ]; // Dereference based on entity type. $data['entity'] = match ($entity->getEntityTypeId()) { 'taxonomy_term' => $this->serializeTaxonomyTerm($entity), 'media' => $this->serializeMediaReference($entity), 'user' => $this->serializeUser($entity), 'node' => $this->serializeNodeReference($entity), 'file' => $this->serializeFileEntity($entity), default => [ 'uuid' => $entity->uuid(), 'type' => $entity->bundle(), 'label' => $entity->label(), ], }; return $data; } /** * Serialize an entity reference revisions field item (paragraphs). * * @param mixed $item * The field item. * * @return array|null * The serialized reference or NULL. */ protected function serializeEntityReferenceRevisions(mixed $item): ?array { $entity = $item->entity; if (!$entity) { return NULL; } if ($entity instanceof ParagraphInterface) { return $this->serializeParagraph($entity); } return [ 'target_id' => (int) $item->target_id, 'target_revision_id' => (int) $item->target_revision_id, 'entity' => [ 'uuid' => $entity->uuid(), 'type' => $entity->bundle(), ], ]; } /** * Serialize a taxonomy term. * * @param \Drupal\taxonomy\TermInterface $term * The taxonomy term. * * @return array * The serialized term data. */ protected function serializeTaxonomyTerm(TermInterface $term): array { return [ 'uuid' => $term->uuid(), 'name' => $term->getName(), 'description' => $term->getDescription(), 'vocabulary' => $term->bundle(), ]; } /** * Serialize a media reference (1 level only - no recursion). * * @param \Drupal\media\MediaInterface $media * The referenced media entity. * * @return array * The serialized media reference data. */ protected function serializeMediaReference(MediaInterface $media): array { $data = [ 'uuid' => $media->uuid(), 'mid' => (int) $media->id(), 'bundle' => $media->bundle(), 'name' => $media->getName(), 'status' => $media->isPublished(), 'created' => $this->formatTimestamp((int) $media->getCreatedTime()), 'changed' => $this->formatTimestamp((int) $media->getChangedTime()), ]; // Get the source field value. $sourceField = $media->getSource()->getConfiguration()['source_field'] ?? NULL; if ($sourceField && $media->hasField($sourceField)) { $sourceValue = $media->get($sourceField)->first(); if ($sourceValue) { $file = $sourceValue->entity ?? NULL; if ($file instanceof FileInterface) { $data['uri'] = $file->getFileUri(); $data['url'] = \Drupal::service('file_url_generator')->generateAbsoluteString($file->getFileUri()); } if (isset($sourceValue->alt)) { $data['alt'] = $sourceValue->alt; } } } return $data; } /** * Serialize a user entity. * * @param \Drupal\user\UserInterface $user * The user entity. * * @return array * The serialized user data. */ protected function serializeUser(UserInterface $user): array { return [ 'uuid' => $user->uuid(), 'uid' => (int) $user->id(), 'name' => $user->getAccountName(), 'display_name' => $user->getDisplayName(), ]; } /** * Serialize a node reference (1 level only). * * @param \Drupal\node\NodeInterface $node * The referenced node. * * @return array * The serialized node reference data. */ protected function serializeNodeReference(NodeInterface $node): array { return [ 'uuid' => $node->uuid(), 'nid' => (int) $node->id(), 'type' => $node->bundle(), 'title' => $node->getTitle(), 'status' => $node->isPublished(), 'created' => $this->formatTimestamp((int) $node->getCreatedTime()), 'changed' => $this->formatTimestamp((int) $node->getChangedTime()), ]; } /** * Serialize a paragraph entity. * * @param \Drupal\paragraphs\ParagraphInterface $paragraph * The paragraph entity. * * @return array * The serialized paragraph data. */ protected function serializeParagraph(ParagraphInterface $paragraph): array { $data = [ 'uuid' => $paragraph->uuid(), 'type' => $paragraph->bundle(), 'fields' => [], ]; $fieldDefinitions = $paragraph->getFieldDefinitions(); foreach ($fieldDefinitions as $fieldName => $definition) { // Skip base paragraph fields. if (in_array($fieldName, ['id', 'uuid', 'revision_id', 'langcode', 'type', 'status', 'created', 'parent_id', 'parent_type', 'parent_field_name', 'behavior_settings', 'default_langcode', 'revision_default', 'revision_translation_affected'], TRUE)) { continue; } $fieldItems = $paragraph->get($fieldName); if ($fieldItems->isEmpty()) { continue; } $data['fields'][$fieldName] = $this->serializeFieldItems($fieldItems); } return $data; } /** * Serialize a file entity. * * @param mixed $file * The file entity. * * @return array|null * The serialized file data or NULL. */ protected function serializeFileEntity(mixed $file): ?array { if (!$file) { return NULL; } return [ 'uuid' => $file->uuid(), 'filename' => $file->getFilename(), 'uri' => $file->getFileUri(), 'filemime' => $file->getMimeType(), 'filesize' => (int) $file->getSize(), ]; } /** * Format a timestamp as ISO 8601. * * @param int $timestamp * The Unix timestamp. * * @return string * The formatted date string. */ protected function formatTimestamp(int $timestamp): string { return date('c', $timestamp); } }