summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.txt2
-rw-r--r--includes/migration.inc45
-rwxr-xr-xmigrate.info2
-rw-r--r--plugins/destinations/file.inc189
-rw-r--r--plugins/destinations/path.inc1
5 files changed, 226 insertions, 13 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 3e87c4d..c92bd94 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -13,6 +13,8 @@ Features and enhancements
MigrationBase::getInstance now takes a machine name rather than a class name.
Migration class names are no longer required to end in 'Migration'.
- #992898 - Pass options to source and destination constructors as arrays.
+File destinations (i.e., migrating directly to the file_managed table, with
+ option copying of the files themselves) are now supported.
Allow migration of comment enable/disable.
Check max_execution_time as well as memory_limit, for graceful exit when
max_execution_time is in play.
diff --git a/includes/migration.inc b/includes/migration.inc
index 6f4f991..d422933 100644
--- a/includes/migration.inc
+++ b/includes/migration.inc
@@ -228,14 +228,10 @@ abstract class Migration extends MigrationBase {
// Call pre-process methods
if ($this->status == Migration::STATUS_IMPORTING) {
- if (method_exists($this->destination, 'preImport')) {
- $this->destination->preImport();
- }
+ $this->preImport();
}
else {
- if (method_exists($this->destination, 'preRollback')) {
- $this->destination->preRollback();
- }
+ $this->preRollback();
}
}
@@ -246,20 +242,45 @@ abstract class Migration extends MigrationBase {
public function endProcess() {
// Call post-process methods
if ($this->status == Migration::STATUS_IMPORTING) {
- if (method_exists($this->destination, 'postImport')) {
- $this->destination->postImport();
- }
+ $this->postImport();
}
else {
- if (method_exists($this->destination, 'postRollback')) {
- $this->destination->postRollback();
- }
+ $this->postRollback();
}
parent::endProcess();
}
/**
+ * Default implementations of pre/post import/rollback methods. These call
+ * the destination methods (if they exist) - when overriding, always
+ * call parent::preImport() etc.
+ */
+ protected function preImport() {
+ if (method_exists($this->destination, 'preRollback')) {
+ $this->destination->preImport();
+ }
+ }
+
+ protected function preRollback() {
+ if (method_exists($this->destination, 'preRollback')) {
+ $this->destination->preRollback();
+ }
+ }
+
+ protected function postImport() {
+ if (method_exists($this->destination, 'postImport')) {
+ $this->destination->postImport();
+ }
+ }
+
+ protected function postRollback() {
+ if (method_exists($this->destination, 'postRollback')) {
+ $this->destination->postRollback();
+ }
+ }
+
+ /**
* Perform a rollback operation - remove migrated items from the destination.
*/
protected function rollback() {
diff --git a/migrate.info b/migrate.info
index f8a373e..7165ee8 100755
--- a/migrate.info
+++ b/migrate.info
@@ -19,6 +19,7 @@ files[] = plugins/destinations/term.inc
files[] = plugins/destinations/user.inc
files[] = plugins/destinations/node.inc
files[] = plugins/destinations/comment.inc
+files[] = plugins/destinations/file.inc
files[] = plugins/destinations/path.inc
files[] = plugins/destinations/fields.inc
files[] = plugins/destinations/table_copy.inc
@@ -27,7 +28,6 @@ files[] = plugins/sources/sql.inc
files[] = plugins/sources/sqlmap.inc
files[] = plugins/sources/mssql.inc
files[] = plugins/sources/xml.inc
-;NYI files[] = plugins/sources/view.inc
files[] = tests/plugins/destinations/comment.test
files[] = tests/plugins/destinations/node.test
files[] = tests/plugins/destinations/term.test
diff --git a/plugins/destinations/file.inc b/plugins/destinations/file.inc
new file mode 100644
index 0000000..1211b9d
--- /dev/null
+++ b/plugins/destinations/file.inc
@@ -0,0 +1,189 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Support for files as destinations.
+ */
+
+/**
+ * Destination class implementing migration into the files table.
+ */
+class MigrateDestinationFile extends MigrateDestinationEntity {
+ /**
+ * Whether to copy a file from the provided URI into the Drupal installation.
+ * If FALSE, the URI is presumed to be local to the Drupal install. If TRUE,
+ * the file will be copied according to the copyScheme value.
+ *
+ * @var boolean
+ */
+ protected $copyFile = FALSE;
+
+ /**
+ * When copying files, the destination scheme/directory. Defaults to 'public://'.
+ *
+ * @var string
+ */
+ protected $copyDestination = 'public://';
+
+ static public function getKeySchema() {
+ return array(
+ 'fid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'file_managed ID',
+ ),
+ );
+ }
+
+ /**
+ * Return an options array for file destinations.
+ *
+ * @param boolean $copy_file
+ * TRUE to have the file copied from the provided URI to Drupal. Defaults to FALSE.
+ * @param string $copy_destination
+ * If $copy_file is TRUE, the scheme/directory to use as the destination for the copied file.
+ * Defaults to 'public://'.
+ * @param string $language
+ * Default language for files created via this destination class.
+ * @param string $text_format
+ * Default text format for files created via this destination class.
+ */
+ static public function options($copy_file, $copy_destination, $language, $text_format) {
+ return compact('copy_file', 'copy_destination', 'language', 'text_format');
+ }
+
+ /**
+ * Basic initialization
+ *
+ * @param array $options
+ * Options applied to files.
+ */
+ public function __construct(array $options = array()) {
+ if (isset($options['copy_file'])) {
+ $this->copyFile = $options['copy_file'];
+ }
+ if (isset($options['copy_destination'])) {
+ $this->copyDestination = $options['copy_destination'];
+ }
+ parent::__construct('file', 'file', $options);
+ }
+
+ /**
+ * Returns a list of fields available to be mapped for the entity type (bundle)
+ *
+ * @return array
+ * Keys: machine names of the fields (to be passed to addFieldMapping)
+ * Values: Human-friendly descriptions of the fields.
+ */
+ public function fields() {
+ $fields = array();
+ // First the core properties
+ $fields['fid'] = t('File: Existing file ID');
+ $fields['uid'] = t('File: Uid of user associated with file');
+ $fields['filename'] = t('File: Name of the file with no path components');
+ $fields['uri'] = t('URI of the file');
+ $fields['filemime'] = t('File: The file\'s MIME type');
+ $fields['status'] = t('File: A bitmapped field indicating the status of the file');
+ $fields['timestamp'] = t('File: UNIX timestamp for the date the file was added');
+
+ // Then add in anything provided by handlers
+ $fields += migrate_handler_invoke_all('Entity', 'fields', $this->entityType, $this->bundle);
+ $fields += migrate_handler_invoke_all('File', 'fields');
+
+ return $fields;
+ }
+
+ /**
+ * Delete a file entry.
+ *
+ * @param array $fid
+ * Fid to delete, arrayed.
+ */
+ public function rollback(array $fid) {
+ migrate_instrument_start('file_delete');
+ $file = file_load(reset($fid));
+ if ($file) {
+ // TODO: Error checking
+ file_delete($file);
+ }
+ migrate_instrument_stop('file_delete');
+ }
+
+ /**
+ * Import a single file record.
+ *
+ * @param $file
+ * File object to build. Prefilled with any fields mapped in the Migration.
+ * @param $row
+ * Raw source data object - passed through to prepare/complete handlers.
+ * @return array
+ * Array of key fields (fid only in this case) of the file that was saved if
+ * successful. FALSE on failure.
+ */
+ public function import(stdClass $file, stdClass $row) {
+ // Updating previously-migrated content?
+ $migration = Migration::currentMigration();
+ if (isset($row->migrate_map_destid1)) {
+ if (isset($file->fid)) {
+ if ($file->fid != $row->migrate_map_destid1) {
+ throw new MigrateException(t("Incoming fid !fid and map destination fid !destid1 don't match",
+ array('!fid' => $file->fid, '!destid1' => $row->migrate_map_destid1)));
+ }
+ }
+ else {
+ $file->fid = $row->migrate_map_destid1;
+ }
+ }
+ if ($migration->getSystemOfRecord() == Migration::DESTINATION) {
+ if (!isset($file->fid)) {
+ throw new MigrateException(t('System-of-record is DESTINATION, but no destination fid provided'));
+ }
+ $old_file = file_load($file->fid);
+ }
+
+ if ($this->copyFile) {
+ $path = trim(parse_url($file->uri, PHP_URL_PATH), '/');
+ $destination = $this->copyDestination . $path;
+ $dirname = drupal_dirname($destination);
+ if (file_prepare_directory($dirname, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ // We'd like to use file_unmanaged_copy, but it calls file_exists, which
+ // won't work for remote URLs
+ migrate_instrument_start('MigrateDestinationFile copy');
+ $result = @copy($file->uri, $destination);
+ migrate_instrument_stop('MigrateDestinationFile copy');
+ if ($result) {
+ $file->uri = $destination;
+ }
+ else {
+ throw new MigrateException(t('Could not copy file !uri', array('!uri' => $file->uri)),
+ MigrationBase::MESSAGE_ERROR);
+ }
+ }
+ else {
+ throw new MigrateException(t('Could not create directory !dirname',
+ array('!dirname' => $dirname)),
+ MigrationBase::MESSAGE_ERROR);
+ }
+ }
+
+ // Default filename to the basename of the (final) URI
+ if (!isset($file->filename) || !$file->filename) {
+ $file->filename = basename($file->uri);
+ }
+
+ // Detect the mime type if not provided
+ if (!isset($file->filemime) || !$file->filemime) {
+ $file->filemime = file_get_mimetype($file->filename);
+ }
+ // Invoke migration prepare handlers
+ $this->prepare($file, $row);
+ migrate_instrument_start('file_save');
+ $file = file_save($file);
+ migrate_instrument_stop('file_save');
+ $this->complete($file, $row);
+ $return = isset($file->fid) ? array($file->fid) : FALSE;
+ return $return;
+ }
+}
diff --git a/plugins/destinations/path.inc b/plugins/destinations/path.inc
index e96f8b2..66b087a 100644
--- a/plugins/destinations/path.inc
+++ b/plugins/destinations/path.inc
@@ -12,6 +12,7 @@ class MigratePathEntityHandler extends MigrateDestinationHandler {
}
public function fields() {
+ // TODO: Only surface when path module is enabled, and for appropriate entities
return array('path' => t('Node: Path alias'));
}