class FileImport in Migrate Files (extended) 8
Same name and namespace in other branches
- 2.0.x src/Plugin/migrate/process/FileImport.php \Drupal\migrate_file\Plugin\migrate\process\FileImport
Imports a file from an local or external source.
Files will be downloaded or copied from the source if necessary and a file entity will be created for it. The file can be moved, reused, or set to be automatically renamed if a duplicate exists.
Required configuration keys:
- source: The source path or URI, e.g. '/path/to/foo.txt' or 'public://bar.txt'.
Optional configuration keys:
- destination: (recommended) The destination path or URI, example: '/path/to/bar/' or 'public://foo.txt'. To provide a directory path (to which the file is saved using its original name), a trailing slash *must* be used to differentiate it from being a filename. If no trailing slash is provided the path will be assumed to be the destination filename. Defaults to "public://".
- uid: The uid to attribute the file entity to. Defaults to 0
- move: Boolean, if TRUE, move the file, otherwise copy the file. Only applies if the source file is local. If the source file is remote it will be copied. Defaults to FALSE.
- rename: Boolean, if TRUE, rename the file by appending a number until the name is unique. Defaults to FALSE.
- reuse: Boolean, if TRUE, reuse the current file in its existing location rather than move/copy/rename the file. Defaults to FALSE.
- skip_on_missing_source: (optional) Boolean, if TRUE, this field will be skipped if the source file is missing (either not available locally or 404 if it's a remote file). Otherwise, the row will fail with an error. Note that if you are importing a lot of remote files, this check will greatly reduce the speed of your import as it requires an http request per file to check for existence. Defaults to FALSE.
- skip_on_error: (optional) Boolean, if TRUE, this field will be skipped if any error occurs during the file import (including missing source files). Otherwise, the row will fail with an error. Defaults to FALSE.
- id_only: (optional) Boolean, if TRUE, the process will return just the id instead of a entity reference array. Useful if you want to manage other sub-fields in your migration (see example below).
The destination and uid configuration fields support copying destination values. These are indicated by a starting @ sign. Values using @ must be wrapped in quotes. (the same as it works with the 'source' property).
Example:
destination:
plugin: entity:node
source:
# assuming we're using a source plugin that lets us define fields like this
fields:
-
name: file
label: 'Some file'
selector: /file
-
name: image
label: 'Main Image'
selector: /image
-
name: text_field_1
label: 'Some Text Value'
selector: /text
-
name: text_field_2
label: 'Another Text Value'
selector: /text_2
constants:
# Note the trailing slash indicates this destination is a directory so
# the filename will be kept intact when copying
file_destination: 'public://path/to/save/'
# This is for creating dynamic destination paths (see below)
directory_separator: '/'
process:
uid:
plugin: default_value
default_value: 1
#
# Simple file import
#
field_file:
plugin: file_import
source: file
destination: constants/file_destination
uid: @uid
skip_on_missing_source: true
#
# Custom field attributes
#
field_image/target_id:
plugin: file_import
source: image
destination: constants/file_destination
uid: @uid
id_only: true
field_image/alt: image
#
# Since the destination property can accept a destination value, you can
# create dynamic filepaths. First you create a temporary field (you can
# name this whatever you want as long as it isn't the name of a field on the
# migrate destination entity/object)
#
_file_destination:
plugin: concat
source:
- constants/file_destination
- constants/directory_separator
- '@text_field_1'
- constants/directory_separator
- '@text_field_2'
- constants/directory_separator
# Now we can use our pseudo temp field as a destination value
field_file:
plugin: file_import
source: file
destination: '@_file_destination'
uid: @uid
skip_on_missing_source: true
Plugin annotation
@MigrateProcessPlugin(
id = "file_import"
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\migrate\ProcessPluginBase implements MigrateProcessInterface
- class \Drupal\migrate\Plugin\migrate\process\FileProcessBase
- class \Drupal\migrate\Plugin\migrate\process\FileCopy implements ContainerFactoryPluginInterface
- class \Drupal\migrate_file\Plugin\migrate\process\FileImport
- class \Drupal\migrate\Plugin\migrate\process\FileCopy implements ContainerFactoryPluginInterface
- class \Drupal\migrate\Plugin\migrate\process\FileProcessBase
- class \Drupal\migrate\ProcessPluginBase implements MigrateProcessInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of FileImport
See also
Drupal\migrate\Plugin\migrate\process\Get
\Drupal\migrate\Plugin\MigrateProcessInterface
File
- src/
Plugin/ migrate/ process/ FileImport.php, line 149
Namespace
Drupal\migrate_file\Plugin\migrate\processView source
class FileImport extends FileCopy {
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system, MigrateProcessInterface $download_plugin) {
$configuration += [
'destination' => NULL,
'uid' => NULL,
'skip_on_missing_source' => FALSE,
'id_only' => FALSE,
];
parent::__construct($configuration, $plugin_id, $plugin_definition, $stream_wrappers, $file_system, $download_plugin);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!$value) {
return NULL;
}
// Get our file entity values.
$source = $value;
$destination = $this
->getPropertyValue($this->configuration['destination'], $row) ?: 'public://';
$uid = $this
->getPropertyValue($this->configuration['uid'], $row) ?: 0;
$id_only = $this->configuration['id_only'];
// If there's no we skip.
if (!$source) {
return NULL;
}
elseif ($this->configuration['skip_on_missing_source'] && !$this
->sourceExists($source)) {
// If we have a source file path, but it doesn't exist, and we're meant
// to just skip processing, we do so, but we log the message.
$migrate_executable
->saveMessage("Source file {$source} does not exist. Skipping.");
return NULL;
}
// Build the destination file uri (in case only a directory was provided).
$destination = $this
->getDestinationFilePath($source, $destination);
if (!$this->fileSystem
->uriScheme($destination)) {
if (empty($destination)) {
$destination = file_default_scheme() . '://' . preg_replace('/^\\//', '', $destination);
}
}
$final_destination = '';
// If we're in re-use mode, reuse the file if it exists.
if ($this
->getOverwriteMode() == FILE_EXISTS_ERROR && $this
->isLocalUri($destination) && is_file($destination)) {
// Look for a file entity with the destination uri.
if ($files = \Drupal::entityTypeManager()
->getStorage('file')
->loadByProperties([
'uri' => $destination,
])) {
// Grab the first file entity with a matching uri.
// @todo: Any logic for preference when there are multiple?
$file = reset($files);
// Set to permanent if the file in the database is set to temporary.
// This means that the file was probably set to be removed during
// garbage collection, which we don't want to happen anymore since we're
// using it.
if (!$file
->isTemporary()) {
$file
->setPermanent();
$file
->save();
}
return $id_only ? $file
->id() : [
'target_id' => $file
->id(),
];
}
else {
$final_destination = $destination;
}
}
else {
// The parent method will take care of our download/move/copy/rename.
// We just need to final destination to create the file object.
try {
$final_destination = parent::transform([
$source,
$destination,
], $migrate_executable, $row, $destination_property);
} catch (MigrateException $e) {
// Check if we're skipping on error
if ($this->configuration['skip_on_error']) {
$migrate_executable
->saveMessage("File {$source} could not be imported to {$destination}. Operation failed with message: " . $e
->getMessage());
throw new MigrateSkipProcessException($e
->getMessage());
}
else {
// Pass the error back on again.
throw new MigrateException($e
->getMessage());
}
}
}
if ($final_destination) {
// Create a file entity.
$file = File::create([
'uri' => $final_destination,
'uid' => $uid,
'status' => FILE_STATUS_PERMANENT,
]);
$file
->save();
return $id_only ? $file
->id() : [
'target_id' => $file
->id(),
];
}
throw new MigrateException("File {$source} could not be imported to {$destination}");
}
/**
* Gets a value from a source or destination property.
*
* Code is adapted from Drupal\migrate\Plugin\migrate\process\Get::transform()
*/
protected function getPropertyValue($property, $row) {
if ($property || (string) $property === '0') {
$is_source = TRUE;
if ($property[0] == '@') {
$property = preg_replace_callback('/^(@?)((?:@@)*)([^@]|$)/', function ($matches) use (&$is_source) {
// If there are an odd number of @ in the beginning, it's a
// destination.
$is_source = empty($matches[1]);
// Remove the possible escaping and do not lose the terminating
// non-@ either.
return str_replace('@@', '@', $matches[2]) . $matches[3];
}, $property);
}
if ($is_source) {
return $row
->getSourceProperty($property);
}
else {
return $row
->getDestinationProperty($property);
}
}
return FALSE;
}
/**
* Determines how to handle file conflicts.
*
* @return int
* FILE_EXISTS_REPLACE (default), FILE_EXISTS_RENAME, or FILE_EXISTS_ERROR
* depending on the current configuration.
*/
protected function getOverwriteMode() {
if (!empty($this->configuration['rename'])) {
return FILE_EXISTS_RENAME;
}
if (!empty($this->configuration['reuse'])) {
return FILE_EXISTS_ERROR;
}
return FILE_EXISTS_REPLACE;
}
/**
* Check if a path is a meant to be a directory.
*
* We're using a trailing slash to indicate the path is a directory. This is
* so that we can create it if it doesn't exist. Without the trailing slash
* there would be no reliable way to know whether or not the path is meant
* to be the target filename since files don't technically _have_ to have
* extensions, and directory names can contain periods.
*/
protected function isDirectory($path) {
return substr($path, -1) == '/';
}
/**
* Build the destination filename.
*
* @param string $source
* The source URI.
*
* @param string $destination
* The destination URI.
*
* @return boolean
* Whether or not the file exists.
*/
protected function getDestinationFilePath($source, $destination) {
if ($this
->isDirectory($destination)) {
$parsed_url = parse_url($source);
$filepath = $destination . drupal_basename($parsed_url['path']);
}
else {
$filepath = $destination;
}
return $filepath;
}
/**
* Check if a source exists.
*/
protected function sourceExists($path) {
if ($this
->isLocalUri($path)) {
return is_file($path);
}
else {
try {
\Drupal::httpClient()
->head($path);
return TRUE;
} catch (ServerException $e) {
return FALSE;
} catch (ClientException $e) {
return FALSE;
} catch (ConnectException $e) {
return FALSE;
}
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
FileCopy:: |
protected | property | An instance of the download process plugin. | |
FileCopy:: |
protected | property | The file system service. | |
FileCopy:: |
protected | property | The stream wrapper manager service. | |
FileCopy:: |
public static | function |
Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface:: |
|
FileCopy:: |
protected | function | Returns the directory component of a URI or path. | |
FileCopy:: |
protected | function | Determines if the given URI or path is considered local. | |
FileCopy:: |
protected | function | Determines if the source and destination URIs represent identical paths. | |
FileCopy:: |
protected | function | Tries to move or copy a file. | |
FileImport:: |
protected | function | Build the destination filename. | |
FileImport:: |
protected | function | Determines how to handle file conflicts. | |
FileImport:: |
protected | function | Gets a value from a source or destination property. | |
FileImport:: |
protected | function | Check if a path is a meant to be a directory. | |
FileImport:: |
protected | function | Check if a source exists. | |
FileImport:: |
public | function |
Performs the associated process. Overrides FileCopy:: |
1 |
FileImport:: |
public | function |
Constructs a file_copy process plugin. Overrides FileCopy:: |
1 |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
ProcessPluginBase:: |
public | function |
Indicates whether the returned value requires multiple handling. Overrides MigrateProcessInterface:: |
3 |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |