diff -u b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php --- b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php @@ -242,6 +242,10 @@ 'source_module' => 'file', 'destination_module' => 'file', ], + 'd7_file_private' => [ + 'source_module' => 'file', + 'destination_module' => 'file', + ], 'd6_filter_format' => [ 'source_module' => 'filter', 'destination_module' => 'filter', diff -u b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php --- b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php +++ b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php @@ -57,8 +57,8 @@ 'configurable_language' => 4, 'contact_form' => 3, 'editor' => 2, - 'field_config' => 49, - 'field_storage_config' => 37, + 'field_config' => 50, + 'field_storage_config' => 38, 'file' => 2, 'filter_format' => 7, 'image_style' => 6, only in patch2: unchanged: --- a/core/modules/file/migration_templates/d7_file.yml +++ b/core/modules/file/migration_templates/d7_file.yml @@ -6,6 +6,7 @@ migration_tags: - Drupal 7 source: plugin: d7_file + scheme: public constants: # source_base_path must be set by the tool configuring this migration. It # represents the fully qualified path relative to which uris in the files only in patch2: unchanged: --- /dev/null +++ b/core/modules/file/migration_templates/d7_file_private.yml @@ -0,0 +1,42 @@ +id: d7_file_private +label: Files +migration_tags: + - Drupal 7 +source: + plugin: d7_file + scheme: private + constants: + # source_file_private_path must be set by the tool configuring this migration. + # It represents the fully qualified path relative to which uris in the files + # table are specified, and must end with a /. See source_full_path + # configuration in this migration's process pipeline as an example. + source_file_private_path: '' +process: + # If you are using this file to build a custom migration consider removing + # the fid field to allow incremental migrations. + fid: fid + filename: filename + source_file_private_full_path: + - + plugin: concat + delimiter: / + source: + - constants/source_file_private_path + - filepath + uri: + plugin: file_copy + source: + - '@source_file_private_full_path' + - uri + filemime: filemime + # filesize is dynamically computed when file entities are saved, so there is + # no point in migrating it. + # filesize: filesize + status: status + # Drupal 7 didn't keep track of the file's creation or update time -- all it + # had was the vague "timestamp" column. So we'll use it for both. + created: timestamp + changed: timestamp + uid: uid +destination: + plugin: entity:file only in patch2: unchanged: --- a/core/modules/file/src/Plugin/migrate/source/d7/File.php +++ b/core/modules/file/src/Plugin/migrate/source/d7/File.php @@ -83,8 +83,12 @@ public function prepareRow(Row $row) { $path = str_replace(['public:/', 'private:/', 'temporary:/'], [$this->publicPath, $this->privatePath, $this->temporaryPath], $row->getSourceProperty('uri')); // At this point, $path could be an absolute path or a relative path, // depending on how the scheme's variable was set. So we need to shear out - // the source_base_path in order to make them all relative. - $path = str_replace($this->configuration['constants']['source_base_path'], NULL, $path); + if (isset($this->configuration['constants']['source_file_private_path'])) { + $path = str_replace($this->configuration['constants']['source_file_private_path'], NULL, $path); + } + else { + $path = str_replace($this->configuration['constants']['source_base_path'], NULL, $path); + } $row->setSourceProperty('filepath', $path); return parent::prepareRow($row); } only in patch2: unchanged: --- /dev/null +++ b/core/modules/file/tests/src/Kernel/Migrate/d7/MigratePrivateFileTest.php @@ -0,0 +1,85 @@ +setSetting('file_private_path', 'sites/default/private'); + $this->installEntitySchema('file'); + $this->container->get('stream_wrapper_manager')->registerWrapper('private', 'Drupal\Core\StreamWrapper\PrivateStream', StreamWrapperInterface::NORMAL); + $fs = \Drupal::service('file_system'); + + // Put test file in the source directory. + file_put_contents('private://sites/default/private/Babylon5.txt', str_repeat('*', 3)); + + /** @var \Drupal\migrate\Plugin\Migration $migration */ + $migration = $this->getMigration('d7_file_private'); + // Set the source plugin's source_file_private_path configuration value, + // which would normally be set by the user running the migration. + $source = $migration->getSourceConfiguration(); + $source['constants']['source_file_private_path'] = $fs->realpath('private://'); + $migration->set('source', $source); + $this->executeMigration($migration); + } + + /** + * Tests a single file entity. + * + * @param int $id + * The file ID. + * @param string $name + * The expected file name. + * @param string $uri + * The expected URI. + * @param string $mime + * The expected MIME type. + * @param int $size + * The expected file size. + * @param int $created + * The expected creation time. + * @param int $changed + * The expected modification time. + * @param int $uid + * The expected owner ID. + */ + protected function assertEntity($id, $name, $uri, $mime, $size, $created, $changed, $uid) { + /** @var \Drupal\file\FileInterface $file */ + $file = File::load($id); + $this->assertTrue($file instanceof FileInterface); + $this->assertSame($name, $file->getFilename()); + $this->assertSame($uri, $file->getFileUri()); + $this->assertTrue(file_exists($uri)); + $this->assertSame($mime, $file->getMimeType()); + $this->assertSame($size, $file->getSize()); + // isPermanent(), isTemporary(), etc. are determined by the status column. + $this->assertTrue($file->isPermanent()); + $this->assertSame($created, $file->getCreatedTime()); + $this->assertSame($changed, $file->getChangedTime()); + $this->assertSame($uid, $file->getOwnerId()); + } + + /** + * Tests that all expected files are migrated. + */ + public function testFileMigration() { + $this->assertEntity(3, 'Babylon5.txt', 'private://Babylon5.txt', 'text/plain', '3', '1486104045', '1486104045', '1'); + } + +} only in patch2: unchanged: --- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php @@ -3426,6 +3426,21 @@ 'translatable' => '0', 'deleted' => '0', )) +->values(array( + 'id' => '25', + 'field_name' => 'field_private_file', + 'type' => 'file', + 'module' => 'file', + 'active' => '1', + 'storage_type' => 'field_sql_storage', + 'storage_module' => 'field_sql_storage', + 'storage_active' => '1', + 'locked' => '0', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:13:"display_field";i:0;s:15:"display_default";i:0;s:10:"uri_scheme";s:7:"private";}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:29:"field_data_field_private_file";a:3:{s:3:"fid";s:22:"field_private_file_fid";s:7:"display";s:26:"field_private_file_display";s:11:"description";s:30:"field_private_file_description";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:33:"field_revision_field_private_file";a:3:{s:3:"fid";s:22:"field_private_file_fid";s:7:"display";s:26:"field_private_file_display";s:11:"description";s:30:"field_private_file_description";}}}}}s:12:"foreign keys";a:1:{s:3:"fid";a:2:{s:5:"table";s:12:"file_managed";s:7:"columns";a:1:{s:3:"fid";s:3:"fid";}}}s:7:"indexes";a:1:{s:3:"fid";a:1:{i:0;s:3:"fid";}}s:2:"id";s:2:"25";}', + 'cardinality' => '1', + 'translatable' => '0', + 'deleted' => '0', +)) ->execute(); $connection->schema()->createTable('field_config_instance', array( @@ -3837,6 +3852,15 @@ 'data' => 'a:7:{s:5:"label";s:14:"Term Reference";s:6:"widget";a:5:{s:6:"weight";s:2:"14";s:4:"type";s:21:"taxonomy_autocomplete";s:6:"module";s:8:"taxonomy";s:6:"active";i:0;s:8:"settings";a:2:{s:4:"size";i:60;s:17:"autocomplete_path";s:21:"taxonomy/autocomplete";}}s:8:"settings";a:1:{s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:4:{s:5:"label";s:5:"above";s:4:"type";s:6:"hidden";s:6:"weight";s:2:"13";s:8:"settings";a:0:{}}}s:8:"required";i:0;s:11:"description";s:0:"";s:13:"default_value";N;}', 'deleted' => '0', )) +->values(array( + 'id' => '42', + 'field_id' => '25', + 'field_name' => 'field_private_file', + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + 'data' => 'a:6:{s:5:"label";s:12:"Private file";s:6:"widget";a:5:{s:6:"weight";s:2:"19";s:4:"type";s:12:"file_generic";s:6:"module";s:4:"file";s:6:"active";i:1;s:8:"settings";a:1:{s:18:"progress_indicator";s:8:"throbber";}}s:8:"settings";a:5:{s:14:"file_directory";s:0:"";s:15:"file_extensions";s:3:"txt";s:12:"max_filesize";s:0:"";s:17:"description_field";i:0;s:18:"user_register_form";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"file_default";s:8:"settings";a:0:{}s:6:"module";s:4:"file";s:6:"weight";i:18;}}s:8:"required";i:0;s:11:"description";s:0:"";}', + 'deleted' => '0', +)) ->execute(); $connection->schema()->createTable('field_data_body', array( @@ -5340,13 +5364,136 @@ 'bundle' => 'test_content_type', 'deleted' => '0', 'entity_id' => '1', - 'revision_id' => '1', + 'revision_id' => '6', 'language' => 'und', 'delta' => '0', 'field_phone_value' => '99-99-99-99', )) ->execute(); +$connection->schema()->createTable('field_data_field_private_file', array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => FALSE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_private_file_fid' => array( + 'type' => 'int', + 'not null' => FALSE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_private_file_display' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '1', + 'unsigned' => TRUE, + ), + 'field_private_file_description' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'normal', + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'field_private_file_fid' => array( + 'field_private_file_fid', + ), + ), + 'mysql_character_set' => 'utf8', +)); + +$connection->insert('field_data_field_private_file') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_private_file_fid', + 'field_private_file_display', + 'field_private_file_description', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '6', + 'language' => 'und', + 'delta' => '0', + 'field_private_file_fid' => '4', + 'field_private_file_display' => '1', + 'field_private_file_description' => '', +)) +->execute(); + $connection->schema()->createTable('field_data_field_tags', array( 'fields' => array( 'entity_type' => array( @@ -7579,6 +7726,140 @@ 'delta' => '0', 'field_phone_value' => '99-99-99-99', )) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '6', + 'language' => 'und', + 'delta' => '0', + 'field_phone_value' => '99-99-99-99', +)) +->execute(); + +$connection->schema()->createTable('field_revision_field_private_file', array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_private_file_fid' => array( + 'type' => 'int', + 'not null' => FALSE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_private_file_display' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '1', + 'unsigned' => TRUE, + ), + 'field_private_file_description' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'normal', + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'field_private_file_fid' => array( + 'field_private_file_fid', + ), + ), + 'mysql_character_set' => 'utf8', +)); + +$connection->insert('field_revision_field_private_file') +->fields(array( + 'entity_type', + 'bundle', + 'deleted', + 'entity_id', + 'revision_id', + 'language', + 'delta', + 'field_private_file_fid', + 'field_private_file_display', + 'field_private_file_description', +)) +->values(array( + 'entity_type' => 'node', + 'bundle' => 'test_content_type', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '6', + 'language' => 'und', + 'delta' => '0', + 'field_private_file_fid' => '4', + 'field_private_file_display' => '1', + 'field_private_file_description' => '', +)) ->execute(); $connection->schema()->createTable('field_revision_field_tags', array( @@ -8365,6 +8646,16 @@ 'status' => '1', 'timestamp' => '1421727516', )) +->values(array( + 'fid' => '3', + 'uid' => '1', + 'filename' => 'Babylon5.txt', + 'uri' => 'private://Babylon5.txt', + 'filemime' => 'text/plain', + 'filesize' => '4', + 'status' => '1', + 'timestamp' => '1486104045', +)) ->execute(); $connection->schema()->createTable('file_usage', array( @@ -8424,14 +8715,14 @@ 'module' => 'file', 'type' => 'node', 'id' => '1', - 'count' => '2', + 'count' => '3', )) ->values(array( 'fid' => '2', 'module' => 'file', 'type' => 'node', 'id' => '1', - 'count' => '1', + 'count' => '2', )) ->values(array( 'fid' => '2', @@ -8440,6 +8731,13 @@ 'id' => '2', 'count' => '1', )) +->values(array( + 'fid' => '3', + 'module' => 'file', + 'type' => 'node', + 'id' => '1', + 'count' => '1', +)) ->execute(); $connection->schema()->createTable('filter', array( @@ -43853,7 +44151,7 @@ )) ->values(array( 'name' => 'file_private_path', - 'value' => 's:0:"";', + 'value' => 's:21:"sites/default/private";', )) ->values(array( 'name' => 'file_public_path',