Compare commits
5 Commits
58b35e5fa6
...
3dc0ff27e9
| Author | SHA1 | Date | |
|---|---|---|---|
| 3dc0ff27e9 | |||
| 7156a490b2 | |||
| 65868007b9 | |||
| 0c6f3f58b4 | |||
| 9006467a9f |
@@ -0,0 +1,14 @@
|
|||||||
|
type: module
|
||||||
|
name: Demo Migration Module
|
||||||
|
description: 'Migrate content from a CSV file. DO NOT INSTALL'
|
||||||
|
package: Migration
|
||||||
|
core: 8.x
|
||||||
|
core_version_requirement: ^8 || ^9
|
||||||
|
dependencies:
|
||||||
|
- migrate
|
||||||
|
- migrate_plus
|
||||||
|
- migrate_tools
|
||||||
|
- migrate_source_csv
|
||||||
|
|
||||||
|
# Hides the module from the Admin UI.
|
||||||
|
hidden: true
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Install and update functions for the Demo Migrate module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_requirements().
|
||||||
|
*/
|
||||||
|
function cdesai_migrate_requirements($phase) {
|
||||||
|
$requirements = [];
|
||||||
|
if ($phase == 'install') {
|
||||||
|
|
||||||
|
// Abort installation of this module since it is not ready.
|
||||||
|
$requirements['cdesai_migrate_not_ready'] = [
|
||||||
|
'description' => t('Demo Migration module is not ready for the installation. This module heavily redacted version of a module used in a project. Do not install.'),
|
||||||
|
'severity' => REQUIREMENT_ERROR,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $requirements;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Settings form
|
||||||
|
cdesai_migrate.settings:
|
||||||
|
title: 'Demo Migrations'
|
||||||
|
description: 'Configure Demo migration settings.'
|
||||||
|
route_name: cdesai_migrate.settings
|
||||||
|
parent: system.admin_config_development
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
administer demo migrate:
|
||||||
|
title: 'Administer Demo Migrate'
|
||||||
|
restrict access: true
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
cdesai_migrate.settings:
|
||||||
|
path: '/admin/config/development/cdesai/migrate'
|
||||||
|
defaults:
|
||||||
|
_form: '\Drupal\cdesai_migrate\Form\CDesaiMigrateSettings'
|
||||||
|
_title: 'Demo Migrations'
|
||||||
|
requirements:
|
||||||
|
_permission: 'administer demo migrate'
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
services:
|
||||||
|
post_migration_subscriber:
|
||||||
|
class: '\Drupal\cdesai_migrate\EventSubscriber\DemoPostMigrationSubscriber'
|
||||||
|
tags:
|
||||||
|
- { name: 'event_subscriber' }
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "drupal/cdesai_migrate",
|
||||||
|
"description": "Demo of custom migration plugins.",
|
||||||
|
"type": "drupal-module",
|
||||||
|
"homepage": "https://wezbyte.com",
|
||||||
|
"keywords": ["Drupal", "Chintan", "Desai", "Migration"],
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chintan Desai",
|
||||||
|
"email": "chintan@wezbyte.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"drupal/core": "^8.9 || ^9.3",
|
||||||
|
"drupal/migrate_file": "^2.0",
|
||||||
|
"drupal/migrate_plus": "^5.1",
|
||||||
|
"drupal/migrate_source_csv": "^3.4",
|
||||||
|
"drupal/migrate_tools": "^5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Migration configuration for importing basic pages.
|
||||||
|
id: cdesai_basic_pages
|
||||||
|
label: Import Basic Pages from CSV
|
||||||
|
migration_group: CDESAI
|
||||||
|
|
||||||
|
source:
|
||||||
|
# The custom CSV source plugin will fetch the source path from configs.
|
||||||
|
# Upload source files at /admin/config/development/cdesai/migrate.
|
||||||
|
plugin: 'cdesai_migrate_csv'
|
||||||
|
source_config_key: cdesai_migrate_basic_pages_source
|
||||||
|
|
||||||
|
delimiter: ','
|
||||||
|
enclosure: '"'
|
||||||
|
header_offset: 0
|
||||||
|
|
||||||
|
ids:
|
||||||
|
- page
|
||||||
|
|
||||||
|
process:
|
||||||
|
langcode: en
|
||||||
|
moderation_state: draft
|
||||||
|
title: title
|
||||||
|
|
||||||
|
uid:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: 1
|
||||||
|
|
||||||
|
field_content/target_id:
|
||||||
|
# Skips a row if skip_migration property is set to TRUE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: skip_migration
|
||||||
|
value: 'TRUE'
|
||||||
|
method: row
|
||||||
|
-
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_paragraph_parents
|
||||||
|
no_stub: true
|
||||||
|
source: page
|
||||||
|
-
|
||||||
|
plugin: extract
|
||||||
|
index:
|
||||||
|
- '0'
|
||||||
|
field_content/target_revision_id:
|
||||||
|
-
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_paragraph_parents
|
||||||
|
no_stub: true
|
||||||
|
source: page
|
||||||
|
-
|
||||||
|
plugin: extract
|
||||||
|
index:
|
||||||
|
- 1
|
||||||
|
|
||||||
|
field_category:
|
||||||
|
# Do not process further if the category value is empty.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: category
|
||||||
|
value: ''
|
||||||
|
method: process
|
||||||
|
|
||||||
|
# The plugin looks up for an exiting term based on the value_key property.
|
||||||
|
# If no matching term is found, it creates one on the fly.
|
||||||
|
-
|
||||||
|
plugin: entity_generate
|
||||||
|
source: category
|
||||||
|
entity_type: taxonomy_term
|
||||||
|
bundle_key: vid
|
||||||
|
bundle: category
|
||||||
|
value_key: name
|
||||||
|
ignore_case: true
|
||||||
|
|
||||||
|
field_legacy_url: legacy_url
|
||||||
|
|
||||||
|
type:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: basic_page
|
||||||
|
|
||||||
|
destination:
|
||||||
|
plugin: 'entity:node'
|
||||||
|
|
||||||
|
# make sure these migration execute before running current migration
|
||||||
|
migration_dependencies:
|
||||||
|
required:
|
||||||
|
- cdesai_paragraphs
|
||||||
|
- cdesai_paragraph_parents
|
||||||
|
|
||||||
|
# use forced module dependency so uninstall/reinstall works properly
|
||||||
|
dependencies:
|
||||||
|
enforced:
|
||||||
|
module:
|
||||||
|
- cdesai_migrate
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Migration configuration for attaching paragraphs to parent paragraphs (row).
|
||||||
|
id: cdesai_paragraph_parents
|
||||||
|
label: 'Parent (Row) Paragraphs'
|
||||||
|
migration_group: CDESAI
|
||||||
|
|
||||||
|
source:
|
||||||
|
# Upload source files at /admin/config/development/cdesai/migrate.
|
||||||
|
plugin: cdesai_paragraph_migrate_source
|
||||||
|
|
||||||
|
process:
|
||||||
|
langcode: en
|
||||||
|
field_bundles/target_id:
|
||||||
|
# Skips a row if skip_migration property is set to TRUE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: skip_migration
|
||||||
|
value: 'TRUE'
|
||||||
|
method: row
|
||||||
|
# Skips a row if scrapped property is set to FALSE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: scrapped
|
||||||
|
value: 'FALSE'
|
||||||
|
method: row
|
||||||
|
-
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_paragraphs
|
||||||
|
no_stub: true
|
||||||
|
source: page
|
||||||
|
-
|
||||||
|
plugin: extract
|
||||||
|
index:
|
||||||
|
- '0'
|
||||||
|
field_bundles/target_revision_id:
|
||||||
|
-
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_paragraphs
|
||||||
|
no_stub: true
|
||||||
|
source: page
|
||||||
|
-
|
||||||
|
plugin: extract
|
||||||
|
index:
|
||||||
|
- '1'
|
||||||
|
|
||||||
|
destination:
|
||||||
|
plugin: 'entity_reference_revisions:paragraph'
|
||||||
|
default_bundle: row
|
||||||
|
|
||||||
|
# make sure these migration execute before running current migration
|
||||||
|
migration_dependencies:
|
||||||
|
required:
|
||||||
|
- cdesai_paragraphs
|
||||||
|
|
||||||
|
# use forced module dependency so uninstall/reinstall works properly
|
||||||
|
dependencies:
|
||||||
|
enforced:
|
||||||
|
module:
|
||||||
|
- cdesai_migrate
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Migration configuration for importing content paragraphs.
|
||||||
|
id: cdesai_paragraphs
|
||||||
|
label: Import Content Paragraphs from Text File Archive
|
||||||
|
migration_group: CDESAI
|
||||||
|
|
||||||
|
source:
|
||||||
|
# Upload source files at /admin/config/development/cdesai/migrate.
|
||||||
|
plugin: cdesai_paragraph_migrate_source
|
||||||
|
|
||||||
|
process:
|
||||||
|
langcode: en
|
||||||
|
|
||||||
|
field_body/value:
|
||||||
|
# Skips a row if skip_migration property is set to TRUE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: skip_migration
|
||||||
|
value: 'TRUE'
|
||||||
|
method: row
|
||||||
|
# Skips a row if scrapped property is set to FALSE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: scrapped
|
||||||
|
value: 'FALSE'
|
||||||
|
method: row
|
||||||
|
# Uses the custom process plugin to extract paragraph contents from a text file.
|
||||||
|
-
|
||||||
|
plugin: extract_file_contents
|
||||||
|
source: content_file
|
||||||
|
|
||||||
|
field_body/format:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: rich_text
|
||||||
|
|
||||||
|
destination:
|
||||||
|
plugin: 'entity_reference_revisions:paragraph'
|
||||||
|
default_bundle: basic
|
||||||
|
|
||||||
|
# use forced module dependency so uninstall/reinstall works properly
|
||||||
|
dependencies:
|
||||||
|
enforced:
|
||||||
|
module:
|
||||||
|
- cdesai_migrate
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# Migration configuration for importing profile pages.
|
||||||
|
id: cdesai_profile_pages
|
||||||
|
label: Import Profile Pages from CSV
|
||||||
|
migration_group: CDESAI
|
||||||
|
|
||||||
|
source:
|
||||||
|
# The custom CSV source plugin will fetch the source path from configs.
|
||||||
|
# Upload source files at /admin/config/development/cdesai/migrate.
|
||||||
|
plugin: 'cdesai_migrate_csv'
|
||||||
|
source_config_key: cdesai_migrate_profile_pages_source
|
||||||
|
|
||||||
|
delimiter: ','
|
||||||
|
enclosure: '"'
|
||||||
|
header_offset: 0
|
||||||
|
|
||||||
|
ids:
|
||||||
|
- page
|
||||||
|
|
||||||
|
process:
|
||||||
|
langcode: en
|
||||||
|
moderation_state: draft
|
||||||
|
title: title
|
||||||
|
field_legacy_url: legacy_url
|
||||||
|
field_year_birth: year_birth
|
||||||
|
field_year_death: year_death
|
||||||
|
|
||||||
|
uid:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: 1
|
||||||
|
|
||||||
|
body/value:
|
||||||
|
# Skips a row if skip_migration property is set to TRUE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: skip_migration
|
||||||
|
value: 'TRUE'
|
||||||
|
method: row
|
||||||
|
# Skips a row if scrapped property is set to FALSE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: scrapped
|
||||||
|
value: 'FALSE'
|
||||||
|
method: row
|
||||||
|
-
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_paragraphs
|
||||||
|
no_stub: true
|
||||||
|
source: page
|
||||||
|
-
|
||||||
|
plugin: extract_cdesai_migrate_paragraph
|
||||||
|
|
||||||
|
field_body/format:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: rich_text
|
||||||
|
|
||||||
|
field_position:
|
||||||
|
# Do not process further if the position value is empty.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: position
|
||||||
|
value: ''
|
||||||
|
method: process
|
||||||
|
|
||||||
|
# The plugin looks up for an exiting term based on the value_key property.
|
||||||
|
# If no matching term is found, it creates one on the fly.
|
||||||
|
-
|
||||||
|
plugin: entity_generate
|
||||||
|
source: position
|
||||||
|
entity_type: taxonomy_term
|
||||||
|
bundle_key: vid
|
||||||
|
bundle: position
|
||||||
|
value_key: name
|
||||||
|
ignore_case: true
|
||||||
|
|
||||||
|
field_agency:
|
||||||
|
# Do not process further if the position value is empty.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: agency
|
||||||
|
value: ''
|
||||||
|
method: process
|
||||||
|
|
||||||
|
# The plugin looks up for an exiting term based on the value_key property.
|
||||||
|
# If no matching term is found, it creates one on the fly.
|
||||||
|
-
|
||||||
|
plugin: entity_generate
|
||||||
|
source: agency
|
||||||
|
entity_type: taxonomy_term
|
||||||
|
bundle_key: vid
|
||||||
|
bundle: agency
|
||||||
|
value_key: name
|
||||||
|
ignore_case: true
|
||||||
|
|
||||||
|
field_years_in_office:
|
||||||
|
-
|
||||||
|
plugin: skip_on_empty
|
||||||
|
source: years_in_office
|
||||||
|
method: process
|
||||||
|
message: 'Row does not contain years_in_office.'
|
||||||
|
-
|
||||||
|
plugin: explode
|
||||||
|
delimiter: ','
|
||||||
|
-
|
||||||
|
plugin: callback
|
||||||
|
callable: trim
|
||||||
|
|
||||||
|
field_media/target_id:
|
||||||
|
# Skips a row if has_images property is set to FALSE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: has_images
|
||||||
|
value: 'FALSE'
|
||||||
|
method: row
|
||||||
|
-
|
||||||
|
plugin: skip_on_empty
|
||||||
|
method: row
|
||||||
|
message: 'Cannot import image file with an empty url.'
|
||||||
|
-
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_profile_pages_media_items
|
||||||
|
source: page
|
||||||
|
|
||||||
|
type:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: profile
|
||||||
|
|
||||||
|
destination:
|
||||||
|
plugin: 'entity:node'
|
||||||
|
|
||||||
|
# make sure these migration execute before running current migration
|
||||||
|
migration_dependencies:
|
||||||
|
required:
|
||||||
|
- cdesai_profile_pages_media_items
|
||||||
|
- cdesai_basic_pages
|
||||||
|
|
||||||
|
# use forced module dependency so uninstall/reinstall works properly
|
||||||
|
dependencies:
|
||||||
|
enforced:
|
||||||
|
module:
|
||||||
|
- cdesai_migrate
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Migration configuration for importing image files.
|
||||||
|
id: cdesai_profile_pages_image_files
|
||||||
|
label: Import Profile Page Files
|
||||||
|
migration_group: CDESAI_MEDIA
|
||||||
|
|
||||||
|
source:
|
||||||
|
# The custom CSV source plugin will fetch the source path from configs.
|
||||||
|
# Upload source files at /admin/config/development/cdesai/migrate.
|
||||||
|
plugin: 'cdesai_migrate_csv'
|
||||||
|
source_config_key: cdesai_migrate_profile_pages_source
|
||||||
|
|
||||||
|
delimiter: ','
|
||||||
|
enclosure: '"'
|
||||||
|
header_offset: 0
|
||||||
|
|
||||||
|
constants:
|
||||||
|
DRUPAL_FILE_DIRECTORY: 'public://images/profile/'
|
||||||
|
|
||||||
|
ids:
|
||||||
|
- page
|
||||||
|
|
||||||
|
process:
|
||||||
|
uid:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: 1
|
||||||
|
|
||||||
|
pseudo_destination_filename:
|
||||||
|
# Skips a row if has_images property is set to FALSE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: has_images
|
||||||
|
value: 'FALSE'
|
||||||
|
method: row
|
||||||
|
-
|
||||||
|
plugin: skip_on_empty
|
||||||
|
method: row
|
||||||
|
message: 'Cannot import image file with an empty url.'
|
||||||
|
-
|
||||||
|
plugin: callback
|
||||||
|
callable: basename
|
||||||
|
source: image_url
|
||||||
|
pseudo_destination_path:
|
||||||
|
-
|
||||||
|
plugin: concat
|
||||||
|
source:
|
||||||
|
- constants/DRUPAL_FILE_DIRECTORY
|
||||||
|
- '@pseudo_destination_filename'
|
||||||
|
uri:
|
||||||
|
-
|
||||||
|
plugin: skip_on_empty
|
||||||
|
method: process
|
||||||
|
source: image_url
|
||||||
|
-
|
||||||
|
plugin: file_copy
|
||||||
|
source:
|
||||||
|
- image_url
|
||||||
|
- '@pseudo_destination_path'
|
||||||
|
file_exists: rename
|
||||||
|
move: false
|
||||||
|
|
||||||
|
destination:
|
||||||
|
plugin: 'entity:file'
|
||||||
|
|
||||||
|
|
||||||
|
# make sure these migration execute before running current migration
|
||||||
|
migration_dependencies: null
|
||||||
|
|
||||||
|
# use forced module dependency so uninstall/reinstall works properly
|
||||||
|
dependencies:
|
||||||
|
enforced:
|
||||||
|
module:
|
||||||
|
- cdesai_migrate
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Migration configuration for creating media items.
|
||||||
|
id: cdesai_profile_pages_media_items
|
||||||
|
label: Import Profile Page Media
|
||||||
|
migration_group: CDESAI_MEDIA
|
||||||
|
|
||||||
|
source:
|
||||||
|
# The custom CSV source plugin will fetch the source path from configs.
|
||||||
|
# Upload source files at /admin/config/development/cdesai/migrate.
|
||||||
|
plugin: 'cdesai_migrate_csv'
|
||||||
|
source_config_key: cdesai_migrate_profile_pages_source
|
||||||
|
|
||||||
|
delimiter: ','
|
||||||
|
enclosure: '"'
|
||||||
|
header_offset: 0
|
||||||
|
|
||||||
|
ids:
|
||||||
|
- page
|
||||||
|
|
||||||
|
process:
|
||||||
|
pseudo_destination_filename:
|
||||||
|
# Skips a row if has_images property is set to FALSE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: has_images
|
||||||
|
value: 'FALSE'
|
||||||
|
method: row
|
||||||
|
-
|
||||||
|
plugin: skip_on_empty
|
||||||
|
method: row
|
||||||
|
message: 'Cannot import image file with an empty url.'
|
||||||
|
-
|
||||||
|
plugin: callback
|
||||||
|
callable: basename
|
||||||
|
source: image_url
|
||||||
|
|
||||||
|
name: '@pseudo_destination_filename'
|
||||||
|
|
||||||
|
field_media_image/target_id:
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_profile_pages_image_files
|
||||||
|
source: page
|
||||||
|
|
||||||
|
field_media_image/alt: title
|
||||||
|
|
||||||
|
thumbnail/target_id:
|
||||||
|
plugin: migration_lookup
|
||||||
|
migration: cdesai_profile_pages_image_files
|
||||||
|
source: page
|
||||||
|
|
||||||
|
thumbnail/alt: title
|
||||||
|
|
||||||
|
uid:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: 1
|
||||||
|
|
||||||
|
langcode: en
|
||||||
|
|
||||||
|
destination:
|
||||||
|
plugin: 'entity:media'
|
||||||
|
default_bundle: image
|
||||||
|
|
||||||
|
# make sure these migration execute before running current migration
|
||||||
|
migration_dependencies:
|
||||||
|
required:
|
||||||
|
- cdesai_profile_pages_image_files
|
||||||
|
|
||||||
|
# use forced module dependency so uninstall/reinstall works properly
|
||||||
|
dependencies:
|
||||||
|
enforced:
|
||||||
|
module:
|
||||||
|
- cdesai_migrate
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Migration configuration for generating redirects from legacy url to Drupal nodes.
|
||||||
|
id: cdesai_migrate_page_redirects
|
||||||
|
label: 'Legacy Page Redirects'
|
||||||
|
migration_group: CDESAI
|
||||||
|
|
||||||
|
source:
|
||||||
|
# Upload source files at /admin/config/development/cdesai/migrate.
|
||||||
|
plugin: cdesai_paragraph_migrate_source
|
||||||
|
|
||||||
|
process:
|
||||||
|
language:
|
||||||
|
plugin: default_value
|
||||||
|
source: language
|
||||||
|
default_value: und
|
||||||
|
|
||||||
|
uid:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: 1
|
||||||
|
|
||||||
|
status_code:
|
||||||
|
plugin: default_value
|
||||||
|
default_value: 301
|
||||||
|
|
||||||
|
redirect_source/path:
|
||||||
|
# Skips a row if skip_migration property is set to TRUE.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: skip_migration
|
||||||
|
value: 'TRUE'
|
||||||
|
method: row
|
||||||
|
# Skips a row if legacy_url property is EMPTY.
|
||||||
|
-
|
||||||
|
plugin: skip_on_value
|
||||||
|
equals: true
|
||||||
|
source: legacy_url
|
||||||
|
value: ''
|
||||||
|
method: row
|
||||||
|
-
|
||||||
|
plugin: substr
|
||||||
|
source: legacy_url
|
||||||
|
start: 1
|
||||||
|
|
||||||
|
redirect_redirect/title: ''
|
||||||
|
|
||||||
|
redirect_redirect/uri:
|
||||||
|
# Uses the custom process plugin to extract paragraph contents from a text file.
|
||||||
|
-
|
||||||
|
plugin: generate_redirect_uri
|
||||||
|
source: legacy_url
|
||||||
|
|
||||||
|
destination:
|
||||||
|
plugin: entity:redirect
|
||||||
|
|
||||||
|
# make sure these migration execute before running current migration
|
||||||
|
migration_dependencies:
|
||||||
|
required:
|
||||||
|
- cdesai_articles
|
||||||
|
- cdesai_basic_pages
|
||||||
|
|
||||||
|
# use forced module dependency so uninstall/reinstall works properly
|
||||||
|
dependencies:
|
||||||
|
enforced:
|
||||||
|
module:
|
||||||
|
- cdesai_migrate
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration form.
|
||||||
|
*/
|
||||||
|
interface DemoMigrateConstantsInterface {
|
||||||
|
const CONFIG_NAME = 'cdesai_migrate.settings';
|
||||||
|
|
||||||
|
const FILE_TYPES = [
|
||||||
|
'spreadsheet' => ['csv'],
|
||||||
|
'archive' => ['zip'],
|
||||||
|
'text' => ['txt'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const SOURCES = [
|
||||||
|
'cdesai_migrate_basic_pages_source' => [
|
||||||
|
'label' => 'Basic Pages Source',
|
||||||
|
'directory' => 'private://migration/sources',
|
||||||
|
'description' => 'File must be a CSV file.',
|
||||||
|
'source_file_type' => 'spreadsheet',
|
||||||
|
'required_columns' => [
|
||||||
|
'page',
|
||||||
|
'skip_migration',
|
||||||
|
'scrapped',
|
||||||
|
'category',
|
||||||
|
'title',
|
||||||
|
'legacy_url',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'cdesai_migrate_profile_pages_source' => [
|
||||||
|
'label' => 'Profile Pages Source',
|
||||||
|
'directory' => 'private://migration/sources',
|
||||||
|
'description' => 'File must be a CSV file.',
|
||||||
|
'source_file_type' => 'spreadsheet',
|
||||||
|
'required_columns' => [
|
||||||
|
'page',
|
||||||
|
'skip_migration',
|
||||||
|
'scrapped',
|
||||||
|
'position',
|
||||||
|
'title',
|
||||||
|
'legacy_url',
|
||||||
|
'year_birth',
|
||||||
|
'year_death',
|
||||||
|
'years_in_office',
|
||||||
|
'agency',
|
||||||
|
'image_url',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'cdesai_migrate_paragraphs_source' => [
|
||||||
|
'label' => 'Paragraphs Source',
|
||||||
|
'directory' => 'private://migration/sources',
|
||||||
|
'description' => 'File must be a ZIP archive containing one or more .txt files.',
|
||||||
|
'source_file_type' => 'archive',
|
||||||
|
'archive_content_file_types' => 'text',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\EventSubscriber;
|
||||||
|
|
||||||
|
use Drupal\migrate\Event\MigrateEvents;
|
||||||
|
use Drupal\migrate\Event\MigratePostRowSaveEvent;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PostMigrationSubscriber.
|
||||||
|
*
|
||||||
|
* Run our user flagging after the last node migration is run.
|
||||||
|
*
|
||||||
|
* @package Drupal\YOUR_MODULE
|
||||||
|
*/
|
||||||
|
class DemoPostMigrationSubscriber implements EventSubscriberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get subscribed events.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public static function getSubscribedEvents(): array {
|
||||||
|
$events[MigrateEvents::POST_ROW_SAVE][] = ['onMigratePostImport'];
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for MigrateEvents::POST_ROW_SAVE event.
|
||||||
|
*
|
||||||
|
* @param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
|
||||||
|
* The import event object.
|
||||||
|
*/
|
||||||
|
public function onMigratePostImport(MigratePostRowSaveEvent $event) {
|
||||||
|
if ($event->getMigration()->getBaseId() == 'cdesai_basic_pages') {
|
||||||
|
// CODE REDACTED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Form;
|
||||||
|
|
||||||
|
use Drupal\cdesai_migrate\DemoMigrateConstantsInterface;
|
||||||
|
use Drupal\Core\Archiver\ArchiverManager;
|
||||||
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||||
|
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||||
|
use Drupal\Core\File\Exception\FileException;
|
||||||
|
use Drupal\Core\File\FileSystemInterface;
|
||||||
|
use Drupal\Core\Form\ConfigFormBase;
|
||||||
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\Core\StreamWrapper\StreamWrapperManager;
|
||||||
|
use Drupal\file\Entity\File;
|
||||||
|
use Drupal\file\FileInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration form.
|
||||||
|
*/
|
||||||
|
class CDesaiMigrateSettings extends ConfigFormBase {
|
||||||
|
const FORM_ID = 'cdesai_migrate_settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The module handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||||
|
*/
|
||||||
|
protected ModuleHandlerInterface $moduleHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file system.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\File\FileSystemInterface
|
||||||
|
*/
|
||||||
|
protected FileSystemInterface $fileSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The archive manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Archiver\ArchiverManager
|
||||||
|
*/
|
||||||
|
protected ArchiverManager $archiveManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a ThemeSettingsForm object.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||||
|
* The factory for configuration objects.
|
||||||
|
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||||
|
* The module handler instance to use.
|
||||||
|
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||||
|
* The file system.
|
||||||
|
* @param \Drupal\Core\Archiver\ArchiverManager $archive_manager
|
||||||
|
* The archive manager.
|
||||||
|
*/
|
||||||
|
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, FileSystemInterface $file_system, ArchiverManager $archive_manager) {
|
||||||
|
parent::__construct($config_factory);
|
||||||
|
|
||||||
|
$this->moduleHandler = $module_handler;
|
||||||
|
$this->fileSystem = $file_system;
|
||||||
|
$this->archiveManager = $archive_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function create(ContainerInterface $container) {
|
||||||
|
return new static(
|
||||||
|
$container->get('config.factory'),
|
||||||
|
$container->get('module_handler'),
|
||||||
|
$container->get('file_system'),
|
||||||
|
$container->get('plugin.manager.archiver')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getEditableConfigNames(): array {
|
||||||
|
return [
|
||||||
|
DemoMigrateConstantsInterface::CONFIG_NAME,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFormId(): string {
|
||||||
|
return self::FORM_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||||
|
$configs = $this->config(DemoMigrateConstantsInterface::CONFIG_NAME);
|
||||||
|
|
||||||
|
foreach (DemoMigrateConstantsInterface::SOURCES as $sourceKey => $source) {
|
||||||
|
|
||||||
|
$currentPath = $configs->get($sourceKey . '_path');
|
||||||
|
$text = $this->t('Upload a file');
|
||||||
|
if ($currentPath && $this->validatePath($currentPath)) {
|
||||||
|
$text = $currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
$form[$sourceKey . '_current_path'] = [
|
||||||
|
'#type' => 'markup',
|
||||||
|
'#markup' =>
|
||||||
|
"<strong>Current {$source['label']} file:</strong> " . $text,
|
||||||
|
];
|
||||||
|
|
||||||
|
$form[$sourceKey . '_upload'] = [
|
||||||
|
'#type' => 'file',
|
||||||
|
'#title' => $this->t('Upload %title file', ['%title' => $source['label']]),
|
||||||
|
'#description' => $source['description'],
|
||||||
|
'#upload_validators' => [
|
||||||
|
'file_validate_extensions' => DemoMigrateConstantsInterface::FILE_TYPES[$source['source_file_type']],
|
||||||
|
],
|
||||||
|
'#suffix' => '<hr/>',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::buildForm($form, $form_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||||
|
parent::validateForm($form, $form_state);
|
||||||
|
|
||||||
|
if ($this->moduleHandler->moduleExists('file')) {
|
||||||
|
foreach (DemoMigrateConstantsInterface::SOURCES as $sourceKey => $source) {
|
||||||
|
// Validate file only if a new file was uploaded.
|
||||||
|
$upload_key = $sourceKey . '_upload';
|
||||||
|
$file = _file_save_upload_from_form($form[$upload_key], $form_state, 0, FileSystemInterface::EXISTS_REPLACE);
|
||||||
|
if ($file) {
|
||||||
|
if ($source['source_file_type'] === 'archive') {
|
||||||
|
$contentFileExts = DemoMigrateConstantsInterface::FILE_TYPES[$source['archive_content_file_types']];
|
||||||
|
$this->validateArchiveFile($upload_key, $contentFileExts, $file, $form_state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->validateCsvFile($upload_key, $source['required_columns'], $file, $form_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||||
|
parent::submitForm($form, $form_state);
|
||||||
|
|
||||||
|
$configs = $this->config(DemoMigrateConstantsInterface::CONFIG_NAME);
|
||||||
|
|
||||||
|
// Exclude unnecessary elements before saving.
|
||||||
|
$form_state->cleanValues();
|
||||||
|
$values = $form_state->getValues();
|
||||||
|
|
||||||
|
foreach (DemoMigrateConstantsInterface::SOURCES as $sourceKey => $source) {
|
||||||
|
// Check for a newly uploaded file.
|
||||||
|
$upload_key = $sourceKey . '_upload';
|
||||||
|
$path_key = $sourceKey . '_path';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isset($values[$upload_key]) && $values[$upload_key] instanceof File) {
|
||||||
|
$this->fileSystem->mkdir($source['directory'], NULL, TRUE);
|
||||||
|
$filename = $this->fileSystem->copy($values[$upload_key]->getFileUri(), $source['directory'], FileSystemInterface::EXISTS_RENAME);
|
||||||
|
$configs->set($path_key, $filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileException $e) {
|
||||||
|
// Ignore.
|
||||||
|
$this->logger('Demo Migration')->error($e->getMessage());
|
||||||
|
$this->messenger()->addError($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$configs->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for the cdesai_migrate_settings form.
|
||||||
|
*
|
||||||
|
* Attempts to validate normal system paths, paths relative to the public
|
||||||
|
* files directory, or stream wrapper URIs. If the given path is any of the
|
||||||
|
* above, returns a valid path or URI that the theme system can display.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* A path relative to the Drupal root or to the public files directory, or
|
||||||
|
* a stream wrapper URI.
|
||||||
|
*
|
||||||
|
* @return false|string
|
||||||
|
* A valid path that can be displayed through the theme system, or FALSE if
|
||||||
|
* the path could not be validated.
|
||||||
|
*/
|
||||||
|
protected function validatePath(string $path) {
|
||||||
|
// Absolute local file paths are invalid.
|
||||||
|
if ($this->fileSystem->realpath($path) == $path) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A path relative to the Drupal root or a fully qualified URI is valid.
|
||||||
|
if (is_file($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend 'private://' for relative file paths within private filesystem.
|
||||||
|
if (StreamWrapperManager::getScheme($path) === FALSE) {
|
||||||
|
$path = 'private://' . $path;
|
||||||
|
|
||||||
|
if (is_file($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the archive file.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* Form field key.
|
||||||
|
* @param array $allowed_file_extensions
|
||||||
|
* File extensions allowed in the archive.
|
||||||
|
* @param \Drupal\file\FileInterface $file
|
||||||
|
* The file being uploaded.
|
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||||
|
* The current state of the form.
|
||||||
|
*/
|
||||||
|
private function validateArchiveFile(string $key, array $allowed_file_extensions, FileInterface $file, FormStateInterface $form_state) {
|
||||||
|
$filepath = $this->fileSystem->realpath($file->getFileUri());
|
||||||
|
/** @var \Drupal\Core\Archiver\ArchiverInterface $archiver */
|
||||||
|
$archiver = $this->archiveManager->getInstance([
|
||||||
|
'filepath' => $filepath,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($archiver && !empty($archiver->listContents())) {
|
||||||
|
$otherExt = FALSE;
|
||||||
|
foreach ($archiver->listContents() as $fileInArchive) {
|
||||||
|
$ext = pathinfo($fileInArchive, PATHINFO_EXTENSION);
|
||||||
|
if (!in_array($ext, $allowed_file_extensions)) {
|
||||||
|
$otherExt = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($otherExt) {
|
||||||
|
$form_state->setErrorByName($key, $this->t('Archive contains one or more files with an invalid extension. Allowed extensions are: %ext', [
|
||||||
|
'%ext' => implode(', ', $allowed_file_extensions),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Put the temporary file in form_values so we can save it
|
||||||
|
// on submit.
|
||||||
|
$form_state->setValue($key, $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$form_state->setErrorByName($key, $this->t('Not a valid archive file.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the CSV file.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* Form field key.
|
||||||
|
* @param array $required_columns
|
||||||
|
* An array column keys that should be in the CSV.
|
||||||
|
* @param \Drupal\file\FileInterface $file
|
||||||
|
* The file being uploaded.
|
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||||
|
* The current state of the form.
|
||||||
|
*/
|
||||||
|
private function validateCsvFile(string $key, array $required_columns, FileInterface $file, FormStateInterface $form_state) {
|
||||||
|
$readFile = FALSE;
|
||||||
|
|
||||||
|
if (($handle = fopen($file->getFileUri(), 'r')) !== FALSE) {
|
||||||
|
$row = fgetcsv($handle, 1000);
|
||||||
|
if ($row !== FALSE) {
|
||||||
|
$readFile = TRUE;
|
||||||
|
|
||||||
|
// Sanitize keys in the header.
|
||||||
|
$header = array_map([__CLASS__, 'sanitizeKey'], $row);
|
||||||
|
// Check for required keys.
|
||||||
|
if ($required_columns) {
|
||||||
|
$missingRequiredKeys = array_diff($required_columns, $header);
|
||||||
|
if (!empty($missingRequiredKeys)) {
|
||||||
|
$form_state->setErrorByName($key, $this->t('CSV file is missing required columns: %columns', [
|
||||||
|
'%columns' => implode(', ', $missingRequiredKeys),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Put the temporary file in form_values so we can save it
|
||||||
|
// on submit.
|
||||||
|
$form_state->setValue($key, $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$readFile) {
|
||||||
|
$form_state->setErrorByName($key, $this->t('Unable to read from the file.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove whitespaces form a string and converts it to lowercase.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* A string to sanitize.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* A sanitized string.
|
||||||
|
*/
|
||||||
|
public static function sanitizeKey(string $key): string {
|
||||||
|
$key = trim($key);
|
||||||
|
$key = str_replace(' ', '_', $key);
|
||||||
|
return strtolower($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\process;
|
||||||
|
|
||||||
|
use Drupal\migrate\MigrateSkipRowException;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Drupal\migrate\ProcessPluginBase;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drush\Drush;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a 'Extract File Contents' migrate process plugin.
|
||||||
|
*
|
||||||
|
* Extracts contents of a file.
|
||||||
|
*
|
||||||
|
* @MigrateProcessPlugin(
|
||||||
|
* id = "extract_file_contents"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class ExtractFileContents extends ProcessPluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||||
|
*/
|
||||||
|
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||||
|
$error = FALSE;
|
||||||
|
$page = $row->get('page');
|
||||||
|
|
||||||
|
if (is_null($value) || trim($value) === '') {
|
||||||
|
$error = "Skipping because content filepath is empty for {$page}.";
|
||||||
|
}
|
||||||
|
elseif (!is_file($value)) {
|
||||||
|
$error = "Skipping because content file is missing for {$page}.";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$contents = file_get_contents($value);
|
||||||
|
if ($contents !== FALSE) {
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = "Skipping because content file is unreadable for {$page}.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
Drush::output()->writeln($error);
|
||||||
|
throw new MigrateSkipRowException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\process;
|
||||||
|
|
||||||
|
use Drupal\migrate\ProcessPluginBase;
|
||||||
|
use Drupal\migrate\MigrateException;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Drupal\paragraphs\Entity\Paragraph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a 'Extract Paragraph' migrate process plugin.
|
||||||
|
*
|
||||||
|
* Extracts content of field_body from a paragraph entity.
|
||||||
|
*
|
||||||
|
* @MigrateProcessPlugin(
|
||||||
|
* id = "extract_cdesai_migrate_paragraph",
|
||||||
|
* handle_multiples = TRUE
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class ExtractFromParagraph extends ProcessPluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
* @throws \Drupal\Core\TypedData\Exception\MissingDataException
|
||||||
|
*/
|
||||||
|
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||||
|
if (!is_array($value)) {
|
||||||
|
throw new MigrateException(sprintf("Input should be an array, instead it was of type '%s'", gettype($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($value) != 2) {
|
||||||
|
throw new MigrateException(sprintf("Input array should have exactly two (2) elements, instead it had '%s'", count($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$paragraph = Paragraph::load($value[0]);
|
||||||
|
|
||||||
|
if ($paragraph->hasField('field_body')) {
|
||||||
|
$body = $paragraph->get('field_body');
|
||||||
|
$item = $body->get(0);
|
||||||
|
|
||||||
|
if ($item) {
|
||||||
|
return $item->getValue()['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new MigrateException("Paragraph does not have field_body.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MigrateException("Unable to find paragraph.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\process;
|
||||||
|
|
||||||
|
use Drupal\Component\Plugin\Exception\PluginException;
|
||||||
|
use Drupal\migrate\MigrateSkipRowException;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Drupal\migrate\ProcessPluginBase;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drush\Drush;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a 'Generate Redirect Uri' migrate process plugin.
|
||||||
|
*
|
||||||
|
* Generates a redirect URI from the legacy url.
|
||||||
|
*
|
||||||
|
* @MigrateProcessPlugin(
|
||||||
|
* id = "generate_redirect_uri"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class GenerateRedirectUri extends ProcessPluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||||
|
*/
|
||||||
|
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||||
|
$redirect = '';
|
||||||
|
|
||||||
|
if (is_null($value) || trim($value) === '') {
|
||||||
|
$error = "Skipping because legacy_url is null or empty.";
|
||||||
|
Drush::output()->writeln($error);
|
||||||
|
throw new MigrateSkipRowException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \Drupal\node\NodeStorageInterface $nodeStorage */
|
||||||
|
try {
|
||||||
|
$nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
|
||||||
|
$nodes = $nodeStorage->loadByProperties([
|
||||||
|
'field_legacy_url' => trim($value),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($nodes)) {
|
||||||
|
$redirect = 'entity:node/' . reset($nodes)->id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (PluginException $e) {
|
||||||
|
$redirect = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$redirect) {
|
||||||
|
$error = sprintf('Skipped... Unable to map legacy url "%s" to a node.', $value);
|
||||||
|
Drush::output()->writeln($error);
|
||||||
|
throw new MigrateSkipRowException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Drush::output()->writeln($redirect);
|
||||||
|
return $redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\source;
|
||||||
|
|
||||||
|
use Drupal\cdesai_migrate\DemoMigrateConstantsInterface;
|
||||||
|
use Drupal\Core\Url;
|
||||||
|
use Drupal\migrate\Plugin\MigrationInterface;
|
||||||
|
use Drupal\migrate_source_csv\Plugin\migrate\source\CSV;
|
||||||
|
use League\Csv\Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source for CSV files (overridden to make source path dynamic).
|
||||||
|
*
|
||||||
|
* @see \Drupal\migrate_source_csv\Plugin\migrate\source\CSV
|
||||||
|
*
|
||||||
|
* Available configuration options:
|
||||||
|
* @see \Drupal\migrate_source_csv\Plugin\migrate\source\CSV for all options.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example with minimal options:
|
||||||
|
* @code
|
||||||
|
* source:
|
||||||
|
* plugin: cdesai_migrate_csv
|
||||||
|
* source_config_key: cdesai_migrate_basic_pages_source
|
||||||
|
* ids: [id]
|
||||||
|
* delimiter: '|'
|
||||||
|
* enclosure: "'"
|
||||||
|
* escape: '`'
|
||||||
|
* header_offset: 0
|
||||||
|
* fields:
|
||||||
|
* -
|
||||||
|
* name: id
|
||||||
|
* label: ID
|
||||||
|
* -
|
||||||
|
* name: country
|
||||||
|
* label: Country
|
||||||
|
*
|
||||||
|
* @MigrateSource(
|
||||||
|
* id = "cdesai_migrate_csv",
|
||||||
|
* source_module = "cdesai_migrate"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class CDesaiCSV extends CSV {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||||
|
$configuration['path'] = 'provided by the configs';
|
||||||
|
|
||||||
|
// Source config key is required.
|
||||||
|
if (empty($this->configuration['source_config_key'])) {
|
||||||
|
throw new \InvalidArgumentException('You must declare the "source_config_key" in your source settings.');
|
||||||
|
}
|
||||||
|
// Source config key must be one of the valid values.
|
||||||
|
elseif (!array_key_exists($this->configuration['source_config_key'], DemoMigrateConstantsInterface::SOURCES)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid value for "source_config_key".');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string representing the source file path.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The file path.
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
return $this->getSourceFileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new CSV reader.
|
||||||
|
*
|
||||||
|
* @return \League\Csv\Reader
|
||||||
|
* The reader.
|
||||||
|
*/
|
||||||
|
protected function createReader(): Reader {
|
||||||
|
$path = $this->getSourceFileName();
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
throw new \RuntimeException(sprintf('File "%s" was not found.', $path));
|
||||||
|
}
|
||||||
|
$csv = fopen($path, 'r');
|
||||||
|
if (!$csv) {
|
||||||
|
throw new \RuntimeException(sprintf('File "%s" could not be opened.', $path));
|
||||||
|
}
|
||||||
|
return Reader::createFromStream($csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get source path from configs.
|
||||||
|
*/
|
||||||
|
private function getSourceFileName(): string {
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
return "REDACTED";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\source;
|
||||||
|
|
||||||
|
use Drupal\cdesai_migrate\DemoMigrateConstantsInterface;
|
||||||
|
use Drupal\Core\Url;
|
||||||
|
use Drupal\migrate\MigrateException;
|
||||||
|
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
|
||||||
|
use Drupal\migrate\Plugin\MigrationInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source plugin to import paragraphs from content files.
|
||||||
|
*
|
||||||
|
* @MigrateSource(
|
||||||
|
* id = "cdesai_paragraph_migrate_source"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class CDesaiParagraphMigrateSource extends SourcePluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The paragraph contents archive file containing one or more text files.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $paragraphsSourceFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CSV source files for pages.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected array $pageSourceFiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||||
|
$configs = \Drupal::config(DemoMigrateConstantsInterface::CONFIG_NAME);
|
||||||
|
$this->paragraphsSourceFile = (string) $configs->get('cdesai_migrate_paragraphs_source_path');
|
||||||
|
$this->pageSourceFiles = [
|
||||||
|
(string) $configs->get('cdesai_migrate_articles_source_path'),
|
||||||
|
(string) $configs->get('cdesai_migrate_basic_pages_source_path'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getIds(): array {
|
||||||
|
return [
|
||||||
|
'page' => [
|
||||||
|
'type' => 'string',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function fields(): array {
|
||||||
|
return [
|
||||||
|
'page' => $this->t("Page"),
|
||||||
|
'skip_migration' => $this->t('Skip Migration'),
|
||||||
|
'scrapped' => $this->t('Scrapped Content'),
|
||||||
|
'content_file' => $this->t('Paragraph Content File'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
$this->initializeIterator();
|
||||||
|
|
||||||
|
return "Content Archive: \n\t{$this->paragraphsSourceFile} \n"
|
||||||
|
. "Page Metadata Files:\n\t" . implode("\n\t", $this->pageSourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
protected function initializeIterator() {
|
||||||
|
$this->requiredFilesExist();
|
||||||
|
$pages = $this->extractPageData();
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
// Migrate needs an Iterator class, not just an array.
|
||||||
|
return new \ArrayIterator($pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure all required source files exists.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
private function requiredFilesExist() {
|
||||||
|
$fileExists = TRUE;
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
if (!$fileExists) {
|
||||||
|
$fileUploadUri = Url::fromRoute('cdesai_migrate.settings')->getInternalPath();
|
||||||
|
throw new MigrateException('Upload required source files at ' . $fileUploadUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves paragraph content files from the archive.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
private function getParagraphContentFiles(string $archive_file): array {
|
||||||
|
$contentFiles = [];
|
||||||
|
$extractedDir = $this->extractArchiveFile($archive_file);
|
||||||
|
/** @var \Drupal\Core\File\FileSystem $fileSystem */
|
||||||
|
$fileSystem = \Drupal::service('file_system');
|
||||||
|
$files = $fileSystem->scanDirectory($extractedDir, '/.*txt/');
|
||||||
|
if (empty($files)) {
|
||||||
|
throw new MigrateException('There was an issue extracting files form ' . $archive_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$key = substr($file->filename, 0, strlen($file->filename) - 4);
|
||||||
|
$contentFiles[$key] = $file->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contentFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the archive in a temp directory.
|
||||||
|
*
|
||||||
|
* @param string $filepath
|
||||||
|
* The file path.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The temp directory path containing the extracted archive file.
|
||||||
|
*/
|
||||||
|
private function extractArchiveFile(string $filepath): string {
|
||||||
|
/** @var \Drupal\Core\Archiver\ArchiverInterface $archiver */
|
||||||
|
$archiveManager = \Drupal::service('plugin.manager.archiver');
|
||||||
|
$tempPath = "";
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
return 'temporary://' . $tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract page data from CSV source files.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An associative array containing page data.
|
||||||
|
*/
|
||||||
|
private function extractPageData(): array {
|
||||||
|
$pages = [];
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
return $pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user