migrate_media_handler-8.x-1.0-rc4/src/MediaMaker.php
src/MediaMaker.php
<?php
namespace Drupal\migrate_media_handler;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* MediaMaker takes file paths and makes file & media entities if possible.
*
* Requires the presence of a text field on all destination media entities
* named `field_original_ref`. This allows for lookup by file hash to avoid
* duplication. This field can be deleted after migration is complete.
*/
class MediaMaker implements ContainerInjectionInterface {
/**
* Entity Type Manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* ConfigFactory service.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* Config variable.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* {@inheritdoc}
*/
public function __construct(EntityTypeManager $entity_manager, ConfigFactory $config_factory) {
$this->entityTypeManager = $entity_manager;
$this->configFactory = $config_factory;
// Pull media setting from config. Can be overridden if necessary.
$this->config = $this->configFactory->get('migrate_media_handler.settings');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('config.factory')
);
}
/**
* Find hash of file contents.
*
* @param string $filepath
* Filepath of file to hash.
*
* @return bool|string
* Return hashed file contents or FALSE.
*/
public function getFileHash(string $filepath) {
$path_info = parse_url($filepath);
// Function sha1_file works for local and http/s filepaths.
$hash = sha1_file($filepath);
// Hash da39a... is the hash of an empty string.
if ($hash == 'da39a3ee5e6b4b0d3255bfef95601890afd80709') {
// Possible @TODO here for AWS S3 buckets, as sha1_file doesn't work.
return FALSE;
}
return $hash;
}
/**
* Find existing media by file hash.
*
* @param string $hash
* File hash to lookup in field_original_ref.
*
* @return bool|\Drupal\media\Entity\Media
* Return media entity or FALSE
*/
public function findExistingMediaByHash(string $hash) {
$media = FALSE;
// Lookup the media entity by hash of file data in field_original_ref.
$query = $this->entityTypeManager->getStorage('media')->getQuery()
->condition('status', 1)
->accessCheck(FALSE)
->condition('field_original_ref', $hash, '=');
$mids = $query->execute();
// If found, `mid` is the reference for the content.
if (!empty($mids)) {
$mid = array_pop($mids);
// This check is to make sure that the media /actually/ exists.
$media = $this->entityTypeManager->getStorage('media')->load($mid);
}
return $media;
}
/**
* Find existing media by file path, using the hash.
*
* @param string $path
* Path from which to generate hash for comparison.
*
* @return bool|\Drupal\media\Entity\Media
* Return media entity or FALSE
*/
public function findExistingMediaByPath(string $path) {
// Hash file, then find by stored hash.
$hash = $this->getFileHash($path);
return $this->findExistingMediaByHash($hash);
}
/**
* Make a file entity out of a file path. Move file to current file directory.
*
* @param string $path
* File path of existing legacy file.
*
* @return bool|\Drupal\file\Entity\File
* File entity or FALSE
*/
public function makeFileEntity(string $path) {
$clean_path = strtok($path, '?');
$destination = str_ireplace($this->config->get('file_source'), $this->config->get('file_dest'), $clean_path);
$filename = pathinfo($clean_path, PATHINFO_BASENAME);
if (file_exists($path)) {
// Create file entity.
$file = $this->entityTypeManager->getStorage('file')->create();
$file->setFileUri($destination);
$file->setFilename($filename);
$file->setMimeType(mime_content_type($path));
$file->setSize(filesize($path));
$file->setOwnerId($this->config->get('file_owner'));
$file->setPermanent();
$file->save();
// Copy file to permanent destination.
$dir = dirname($destination);
if (!file_exists($dir)) {
mkdir($dir, 0770, TRUE);
}
file_put_contents($destination, file_get_contents($path));
$file->save();
return $file;
}
return FALSE;
}
/**
* Make an image media entity out of a file. Add alt, title if avail.
*
* @param int $file_id
* ID of existing file entity.
* @param Drupal\migrate\Row $row
* Migration row, used for getting data.
* @param array $configuration
* Config of process plugin.
*
* @return bool|\Drupal\media\Entity\Media
* Return media entity or FALSE
*/
public function makeImageEntity(int $file_id, Row $row, array $configuration) {
$media = FALSE;
// Load file entity.
$file = $this->entityTypeManager->getStorage('file')->load($file_id);
// If that's successful, carry on.
if ($file) {
// Hash file for overlap lookup.
$hash = $this->getFileHash($file->getFileUri());
// Lookup media entity by file hash.
$media = $this->findExistingMediaByHash($hash);
// If it wasn't found, make it!
if (!$media) {
if (isset($configuration['source_field'])) {
$field_source = $row->getSourceProperty($configuration['source_field']);
$alt = $field_source[0]['alt'] ?? "";
$title = $field_source[0]['title'] ?? "";
} else {
$alt = $title = $file->getFilename();
}
// Create media entity with saved file.
$bundle = $configuration['target_bundle'] ?? 'image';
$langcode = $row->getSourceProperty('language');
// Please note accessibility concerns around empty alt & title.
$media = $this->entityTypeManager->getStorage('media')->create([
'bundle' => $bundle,
'field_original_ref' => $hash,
$this->config->get('image_field_name') => [
'target_id' => $file_id,
'alt' => $alt,
'title' => $title,
],
'langcode' => $langcode ?? 'und',
]);
$owner = $file->getOwnerId();
$filename = $file->getFilename();
$media->setOwnerId($owner);
$media->setName($filename);
$media->save();
}
}
return $media;
}
/**
* Make a document media entity out of a file.
*
* @param string $file_id
* ID of existing file entity.
* @param Drupal\migrate\Row $row
* Migration row, used for getting data.
* @param array $configuration
* Config of process plugin.
*
* @return bool|\Drupal\media\Entity\Media
* Return media entity or FALSE
*/
public function makeDocumentEntity(int $file_id, Row $row, array $configuration) {
$media = FALSE;
// Load file entity.
$file = $this->entityTypeManager->getStorage('file')->load($file_id);
// If that's successful, carry on.
if ($file) {
$hash = $this->getFileHash($file->getFileUri());
// Lookup media entity by file hash.
$media = $this->findExistingMediaByHash($hash);
// If it wasn't found, make it!
if (!$media) {
$display = $description = '';
if (isset($configuration['source_field'])) {
$field_source = $row->getSourceProperty($configuration['source_field']);
$display = $field_source[0]['display'] ?? "";
$description = $field_source[0]['description'] ?? "";
}
// Create media entity with saved file.
$bundle = $configuration['target_bundle'] ?? 'document';
$langcode = $row->getSourceProperty('language');
$media = $this->entityTypeManager->getStorage('media')->create([
'bundle' => $bundle,
'field_original_ref' => $hash,
$this->config->get('document_field_name') => [
'target_id' => $file_id,
'display' => $display,
'description' => $description,
],
'langcode' => $langcode ?? 'und',
]);
// Get additional media properties.
$owner = $file->getOwnerId();
$filename = $file->getFilename();
// Set additional media properties.
$media->setOwnerId($owner);
$media->setName($filename);
$media->save();
}
}
return $media;
}
/**
* Make a video media entity out of a url.
*
* @param array $field_data
* Array of data from the D7 video embed field.
* @param Drupal\migrate\Row $row
* Migration row, used for getting data.
* @param array $configuration
* Config of process plugin.
*
* @return bool|\Drupal\media\Entity\Media
* Return media entity or FALSE
*/
public function makeVideoLinkEntity(array $field_data, Row $row, $configuration = []) {
$media = FALSE;
if (!empty($field_data['video_url'])) {
$hash = $this->getFileHash($field_data['video_url']);
// Lookup media entity by hash.
$media = $this->findExistingMediaByHash($hash);
// If it wasn't found, make it!
if (!$media) {
$title = $row->getSourceProperty('title');
$langcode = $row->getSourceProperty('language');
// Create media entity with url.
$bundle = $configuration['target_bundle'] ?? 'remote_video';
$media = $this->entityTypeManager->getStorage('media')->create([
'name' => $title,
'bundle' => $bundle,
'field_original_ref' => $hash,
$this->config->get('video_field_name') => $field_data['video_url'],
'langcode' => $langcode ?? 'und',
]);
$media->save();
}
}
return $media;
}
/**
* Make an audio media entity out of a file.
*
* @param string $file_id
* ID of existing file entity.
* @param Drupal\migrate\Row $row
* Migration row, used for getting data.
* @param array $configuration
* Config of process plugin.
*
* @return bool|\Drupal\media\Entity\Media
* Return media entity or FALSE
*/
public function makeAudioEntity(int $file_id, Row $row, array $configuration) {
$media = FALSE;
// Load file entity.
$file = $this->entityTypeManager->getStorage('file')->load($file_id);
// If that's successful, carry on.
if ($file) {
// Lookup media entity by file hash.
$hash = $this->getFileHash($file->getFileUri());
$media = $this->findExistingMediaByHash($hash);
// If it wasn't found, make it!
if (!$media) {
$display = $description = '';
if (isset($configuration['source_field'])) {
$field_source = $row->getSourceProperty($configuration['source_field']);
$display = $field_source[0]['display'] ?? "";
$description = $field_source[0]['description'] ?? "";
}
// Create media entity with saved file.
$bundle = $configuration['target_bundle'] ?? 'audio';
$langcode = $row->getSourceProperty('language');
// Create media entity with saved file.
$media = $this->entityTypeManager->getStorage('media')->create([
'bundle' => $bundle,
'field_original_ref' => $hash,
$this->config->get('audio_field_name') => [
'target_id' => $file_id,
'display' => $display,
'description' => $description,
],
'langcode' => $langcode ?? 'und',
]);
// Get additional media properties.
$owner = $file->getOwnerId();
$filename = $file->getFilename();
// Set additional media properties.
$media->setOwnerId($owner);
$media->setName($filename);
$media->save();
}
}
return $media;
}
/**
* Utility function to get source file repository filepath.
*
* @param string $path
* Path from which to generate hash for comparison.
* @param bool $find_in_store
* Replace "public://" with file_source variable.
*
* @return string
* Modified file path.
*/
public function getSourceFilePath($path = '', $find_in_store = FALSE) {
$file_path = strtok($path, '?');
// If link is full-path (contains http:// or https://).
if (preg_match('/https?:\/\//', $path) !== 0) {
// If link is full-path to *this* site.
if (preg_match($this->config->get('site_uri'), $path) !== 0) {
// Swap out full path for public://, and remove sites/default/files.
$file_path = preg_replace($this->config->get('site_uri'), $this->config->get('file_source'), $path);
$file_path = str_ireplace("/sites/default/files", "", $file_path);
}
}
// If file path is in the VFSStream protocol, skip.
elseif (preg_match('/vfs:\/\//', $path) !== 0) {
// Do nothing - this is an internal file path that works out of the box.
}
// If this is a relative filepath, swap out sites/default/files.
else {
$file_path = str_ireplace("/sites/default/files", $this->config->get('file_source'), $file_path);
}
return $file_path;
}
/**
* Record a file hash for later usage.
*
* @param int $file_id
* ID of existing file entity.
* @param Drupal\migrate\Row $row
* Migration row, used for getting data.
* @param array $configuration
* Config of process plugin.
*
* @return string
* Return hashed file path.
*/
public function recordMediaRef(int $file_id, Row $row, array $configuration) {
$hash = FALSE;
// Load file entity.
$file = $this->entityTypeManager->getStorage('file')->load($file_id);
// If that's successful, carry on.
if ($file) {
// Hash file for overlap lookup.
$hash = $this->getFileHash($file->getFileUri());
}
return $hash;
}
}
