contrib modules updates

This commit is contained in:
2019-02-27 10:39:59 +01:00
parent 04a4b8895d
commit e3cf889820
579 changed files with 18343 additions and 4076 deletions
@@ -7,8 +7,8 @@ Extensions to base API
migration configuration.
* A MigrationGroup configuration entity is provided, which enables migrations to
be organized in groups, and to maintain shared configuration in one place.
* A MigrateEvents::PREPARE_ROW event is provided to dispatch hook_prepare_row()
invocations as events.
* A MigrateEvents::PREPARE_ROW event is provided to dispatch
hook_migrate_prepare_row() invocations as events.
* A SourcePluginExtension class is provided, enabling one to define fields and
IDs for a source plugin via configuration rather than requiring PHP code.
@@ -8,6 +8,9 @@ migrate_plus.destination.*:
type: boolean
label: 'Whether stubbing is allowed.'
default: false
default_bundle:
type: string
label: 'The default bundle for content entity destinations.'
migrate_plus.destination.config:
type: migrate_destination
@@ -29,13 +29,13 @@ migrate_plus.migration.*:
type: label
label: 'Label'
source:
type: migrate_plus.source.[plugin]
type: ignore
label: 'Source'
process:
type: ignore
label: 'Process'
destination:
type: migrate_plus.destination.[plugin]
type: ignore
label: 'Destination'
migration_dependencies:
type: mapping
@@ -0,0 +1,20 @@
# Learn to make one for your own drupal.org project:
# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing
build:
assessment:
validate_codebase:
phplint:
container_composer:
phpcs:
# phpcs will use core's specified version of Coder.
sniff-all-files: true
halt-on-fail: true
testing:
# run_tests task is executed several times in order of performance speeds.
# halt-on-fail can be set on the run_tests tasks in order to fail fast.
# suppress-deprecations is false in order to be alerted to usages of
# deprecated code.
run_tests.standard:
types: 'Simpletest,PHPUnit-Unit,PHPUnit-Kernel,PHPUnit-Functional'
testgroups: '--all'
suppress-deprecations: false
@@ -10,8 +10,8 @@ dependencies:
- drupal:menu_ui
- drupal:path
# Information added by Drupal.org packaging script on 2018-09-06
version: '8.x-4.0'
# Information added by Drupal.org packaging script on 2019-01-07
version: '8.x-4.1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1536264189
datestamp: 1546879116
@@ -11,8 +11,8 @@ dependencies:
- drupal:options
- drupal:taxonomy
# Information added by Drupal.org packaging script on 2018-09-06
version: '8.x-4.0'
# Information added by Drupal.org packaging script on 2019-01-07
version: '8.x-4.1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1536264189
datestamp: 1546879116
@@ -316,7 +316,7 @@ function migrate_example_beer_data_node() {
'image_title',
'image_description',
];
$query = db_insert('migrate_example_beer_node')
$query = \Drupal::database()->insert('migrate_example_beer_node')
->fields($fields);
// Use high bid numbers to avoid overwriting an existing node id.
$data = [
@@ -381,7 +381,7 @@ function migrate_example_beer_data_account() {
'sex',
'beers',
];
$query = db_insert('migrate_example_beer_account')
$query = \Drupal::database()->insert('migrate_example_beer_account')
->fields($fields);
$data = [
[
@@ -436,7 +436,7 @@ function migrate_example_beer_data_account() {
*/
function migrate_example_beer_data_comment() {
$fields = ['bid', 'cid_parent', 'subject', 'body', 'name', 'mail', 'aid'];
$query = db_insert('migrate_example_beer_comment')
$query = \Drupal::database()->insert('migrate_example_beer_comment')
->fields($fields);
$data = [
[99999998, NULL, 'im first', 'full body', 'alice', 'alice@example.com', 0],
@@ -464,7 +464,7 @@ function migrate_example_beer_data_comment() {
*/
function migrate_example_beer_data_topic() {
$fields = ['style', 'details', 'style_parent', 'region', 'hoppiness'];
$query = db_insert('migrate_example_beer_topic')
$query = \Drupal::database()->insert('migrate_example_beer_topic')
->fields($fields);
$data = [
['ale', 'traditional', NULL, 'Medieval British Isles', 'Medium'],
@@ -488,7 +488,7 @@ function migrate_example_beer_data_topic() {
*/
function migrate_example_beer_data_topic_node() {
$fields = ['bid', 'style'];
$query = db_insert('migrate_example_beer_topic_node')
$query = \Drupal::database()->insert('migrate_example_beer_topic_node')
->fields($fields);
$data = [
[99999999, 'pilsner'],
@@ -1,59 +0,0 @@
# This migration demonstrates importing from an endpoint listing other endpoints
# containing individual item data.
id: wine_variety_list
label: XML feed of varieties
migration_group: wine
migration_tags:
- advanced example
source:
# We use the XML source plugin.
plugin: xml
# Normally, this is one or more fully-qualified URLs or file paths. Because
# we can't hardcode your local URL, we provide a relative path here which
# hook_install() will rewrite to a full URL for the current site.
urls:
- /migrate_example_advanced_variety_list?_format=xml
item_url: /migrate_example_advanced_variety_list/:id?_format=xml
id_selector: /response/items
# Visit the URL above (relative to your site root) and look at it. You can see
# that <response> is the outer element, and each item we want to import is a
# <position> element. The item_xpath value is the xpath to use to query the
# desired elements.
item_selector: /response/variety
# Under 'fields', we list the data items to be imported. The first level keys
# are the source field names we want to populate (the names to be used as
# sources in the process configuration below). For each field we're importing,
# we provide a label (optional - this is for display in migration tools) and
# an xpath for retrieving that value. It's important to note that this xpath
# is relative to the elements retrieved by item_xpath.
fields:
category_name:
label:
selector: name
category_details:
label:
selector: details
category_parent:
label: 'Unique position identifier'
selector: parent
# Under 'ids', we identify source fields populated above which will uniquely
# identify each imported item. The 'type' makes sure the migration map table
# uses the proper schema type for stored the IDs.
ids:
category_name:
type: string
process:
vid:
plugin: default_value
default_value: migrate_example_wine_varieties
name: category_name
description: category_details
parent:
plugin: migration_lookup
migration: wine_terms
source: category_parent
destination:
plugin: entity:taxonomy_term
migration_dependencies:
require:
- wine_terms
@@ -8,8 +8,8 @@ dependencies:
- migrate_plus:migrate_example_advanced_setup
- migrate_plus:migrate_plus
# Information added by Drupal.org packaging script on 2018-09-06
version: '8.x-4.0'
# Information added by Drupal.org packaging script on 2019-01-07
version: '8.x-4.1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1536264189
datestamp: 1546879116
@@ -11,8 +11,8 @@ dependencies:
- drupal:taxonomy
- drupal:rest
# Information added by Drupal.org packaging script on 2018-09-06
version: '8.x-4.0'
# Information added by Drupal.org packaging script on 2019-01-07
version: '8.x-4.1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1536264189
datestamp: 1546879116
@@ -737,7 +737,7 @@ function migrate_example_advanced_data_wine() {
'region',
'rating',
];
$query = db_insert('migrate_example_wine')
$query = \Drupal::database()->insert('migrate_example_wine')
->fields($fields);
$data = [
[
@@ -776,7 +776,7 @@ function migrate_example_advanced_data_wine() {
*/
function migrate_example_advanced_data_updates() {
$fields = ['wineid', 'rating'];
$query = db_insert('migrate_example_advanced_updates')
$query = \Drupal::database()->insert('migrate_example_advanced_updates')
->fields($fields);
$data = [
[1, 93],
@@ -793,7 +793,7 @@ function migrate_example_advanced_data_updates() {
*/
function migrate_example_advanced_data_producer() {
$fields = ['producerid', 'name', 'body', 'excerpt', 'accountid'];
$query = db_insert('migrate_example_advanced_producer')
$query = \Drupal::database()->insert('migrate_example_advanced_producer')
->fields($fields);
$data = [
[1, 'Montes', 'Fine Chilean winery', 'Great!', 9],
@@ -824,7 +824,7 @@ function migrate_example_advanced_data_account() {
'imageid',
'positions',
];
$query = db_insert('migrate_example_advanced_account')
$query = \Drupal::database()->insert('migrate_example_advanced_account')
->fields($fields);
$data = [
[
@@ -884,7 +884,7 @@ function migrate_example_advanced_data_account() {
*/
function migrate_example_advanced_data_account_updates() {
$fields = ['accountid', 'sex'];
$query = db_insert('migrate_example_advanced_account_updates')
$query = \Drupal::database()->insert('migrate_example_advanced_account_updates')
->fields($fields);
$data = [
[1, NULL],
@@ -915,7 +915,7 @@ function migrate_example_advanced_data_comment() {
'posted',
'lastchanged',
];
$query = db_insert('migrate_example_advanced_comment')
$query = \Drupal::database()->insert('migrate_example_advanced_comment')
->fields($fields);
$data = [
[
@@ -1000,7 +1000,7 @@ function migrate_example_advanced_data_comment() {
*/
function migrate_example_advanced_data_comment_updates() {
$fields = ['commentid', 'subject'];
$query = db_insert('migrate_example_advanced_comment_updates')
$query = \Drupal::database()->insert('migrate_example_advanced_comment_updates')
->fields($fields);
$data = [
[1, 'I am first'],
@@ -1027,7 +1027,7 @@ function migrate_example_advanced_data_categories() {
'details',
'ordering',
];
$query = db_insert('migrate_example_advanced_categories')
$query = \Drupal::database()->insert('migrate_example_advanced_categories')
->fields($fields);
$data = [
[
@@ -1079,7 +1079,7 @@ function migrate_example_advanced_data_categories() {
*/
function migrate_example_advanced_data_vintages() {
$fields = ['wineid', 'vintage'];
$query = db_insert('migrate_example_advanced_vintages')
$query = \Drupal::database()->insert('migrate_example_advanced_vintages')
->fields($fields);
$data = [
[1, 2006],
@@ -1097,7 +1097,7 @@ function migrate_example_advanced_data_vintages() {
*/
function migrate_example_advanced_data_variety_updates() {
$fields = ['categoryid', 'details'];
$query = db_insert('migrate_example_advanced_variety_updates')
$query = \Drupal::database()->insert('migrate_example_advanced_variety_updates')
->fields($fields);
$data = [
[1, 'White wines are simpler and sweeter than red'],
@@ -1120,7 +1120,7 @@ function migrate_example_advanced_data_variety_updates() {
*/
function migrate_example_advanced_data_category_wine() {
$fields = ['wineid', 'categoryid'];
$query = db_insert('migrate_example_advanced_category_wine')
$query = \Drupal::database()->insert('migrate_example_advanced_category_wine')
->fields($fields);
$data = [
[1, 12],
@@ -1138,7 +1138,7 @@ function migrate_example_advanced_data_category_wine() {
*/
function migrate_example_advanced_data_category_producer() {
$fields = ['producerid', 'categoryid'];
$query = db_insert('migrate_example_advanced_category_producer')
$query = \Drupal::database()->insert('migrate_example_advanced_category_producer')
->fields($fields);
$data = [
[1, 17],
@@ -1154,7 +1154,7 @@ function migrate_example_advanced_data_category_producer() {
*/
function migrate_example_advanced_data_files() {
$fields = ['imageid', 'url', 'image_alt', 'image_title', 'wineid'];
$query = db_insert('migrate_example_advanced_files')
$query = \Drupal::database()->insert('migrate_example_advanced_files')
->fields($fields);
$data = [
[
@@ -1192,7 +1192,7 @@ function migrate_example_advanced_data_files() {
function migrate_example_advanced_data_blobs() {
$blob = file_get_contents('core/misc/druplicon.png');
$fields = ['imageid', 'imageblob'];
$query = db_insert('migrate_example_advanced_blobs')
$query = \Drupal::database()->insert('migrate_example_advanced_blobs')
->fields($fields);
$data = [
[1, $blob],
@@ -1208,7 +1208,7 @@ function migrate_example_advanced_data_blobs() {
*/
function migrate_example_advanced_data_table_source() {
$fields = ['fooid', 'field1', 'field2'];
$query = db_insert('migrate_example_advanced_table_source')
$query = \Drupal::database()->insert('migrate_example_advanced_table_source')
->fields($fields);
$data = [
[3, 'Some sample data', 58],
@@ -0,0 +1,22 @@
A demonstration of a simple import of a JSON file.
REQUIREMENTS
============
You need the contrib modules Migrate Plus and Migrate Tools.
To make the products.json file available for import, the file will be copied
from the artifacts folder to your sites/default/files folder.
USAGE
=====
Enable the module, check status, import all products and rollback with Drush
drush en migrate_json_example
drush migrate-status
drush migrate-import product
drush migrate-rollback product
See config/optional/migrate_plus.migration.product.yml for details about the
migration.
Thanks to Jeff Geerling and Christophe for the original code:
- https://www.jeffgeerling.com/blog/2016/migrate-custom-json-feed-drupal-8-migrate-source-json
- https://colorfield.be/blog/drupal-8-json-custom-migration
@@ -0,0 +1,16 @@
{
"product": [
{
"upc": "11111",
"name": "Widget",
"description": "Helpful for many things.",
"price": "14.99"
},
{
"upc": "22222",
"name": "Sprocket",
"description": "Helpful for things needing sprockets.",
"price": "8.99"
}
]
}
@@ -0,0 +1,88 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.product.field_description
- field.field.node.product.field_price
- field.field.node.product.field_upc
- node.type.product
module:
- path
id: node.product.default
targetEntityType: node
bundle: product
mode: default
content:
created:
type: datetime_timestamp
weight: 10
region: content
settings: { }
third_party_settings: { }
field_description:
weight: 123
settings:
size: 60
placeholder: ''
third_party_settings: { }
type: string_textfield
region: content
field_price:
weight: 124
settings:
placeholder: ''
third_party_settings: { }
type: number
region: content
field_upc:
weight: 122
settings:
placeholder: ''
third_party_settings: { }
type: number
region: content
path:
type: path
weight: 30
region: content
settings: { }
third_party_settings: { }
promote:
type: boolean_checkbox
settings:
display_label: true
weight: 15
region: content
third_party_settings: { }
status:
type: boolean_checkbox
settings:
display_label: true
weight: 120
region: content
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
region: content
third_party_settings: { }
title:
type: string_textfield
weight: -5
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
region: content
third_party_settings: { }
hidden: { }
@@ -0,0 +1,49 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.product.field_description
- field.field.node.product.field_price
- field.field.node.product.field_upc
- node.type.product
module:
- user
id: node.product.default
targetEntityType: node
bundle: product
mode: default
content:
field_description:
weight: 103
label: above
settings:
link_to_entity: false
third_party_settings: { }
type: string
region: content
field_price:
weight: 104
label: above
settings:
thousand_separator: ''
decimal_separator: .
scale: 2
prefix_suffix: true
third_party_settings: { }
type: number_decimal
region: content
field_upc:
weight: 102
label: above
settings:
thousand_separator: ''
prefix_suffix: true
third_party_settings: { }
type: number_integer
region: content
links:
weight: 100
settings: { }
third_party_settings: { }
region: content
hidden: { }
@@ -0,0 +1,18 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_description
- node.type.product
id: node.product.field_description
field_name: field_description
entity_type: node
bundle: product
label: Description
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string
@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_price
- node.type.product
id: node.product.field_price
field_name: field_price
entity_type: node
bundle: product
label: Price
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
min: null
max: null
prefix: ''
suffix: ''
field_type: float
@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_upc
- node.type.product
id: node.product.field_upc
field_name: field_upc
entity_type: node
bundle: product
label: UPC
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
min: null
max: null
prefix: ''
suffix: ''
field_type: integer
@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
module:
- node
id: node.field_description
field_name: field_description
entity_type: node
type: string
settings:
max_length: 255
is_ascii: false
case_sensitive: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,17 @@
langcode: en
status: true
dependencies:
module:
- node
id: node.field_price
field_name: field_price
entity_type: node
type: float
settings: { }
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
module:
- node
id: node.field_upc
field_name: field_upc
entity_type: node
type: integer
settings:
unsigned: false
size: normal
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,81 @@
# This migration demonstrates a simple import from a JSON file.
id: product
label: JSON feed of Products
migration_group: Product
migration_tags:
- json example
source:
# We use the JSON source plugin.
plugin: url
# In this example we get data from a local file, to get data from a URL
# define http as data_fetcher_plugin.
# data_fetcher_plugin: http
data_fetcher_plugin: file
data_parser_plugin: json
# The data_parser normally limits the fields passed on to the source plugin
# to fields configured to be used as part of the migration. To support more
# dynamic migrations, the JSON data parser supports including the original
# data for the current row. Simply include the 'include_raw_data' flag set
# to `true` to enable this. This option is disabled by default to minimize
# memory footprint for migrations that do not need this capability.
# include_raw_data: true
# Flags whether to track changes to incoming data. If TRUE, we will maintain
# hashed source rows to determine whether incoming data has changed.
# track_changes: true
# Copy the example JSON file in artifacts folder to sites/default/files folder.
urls:
- 'public://migrate_json_example/products.json'
# An xpath-like selector corresponding to the items to be imported.
item_selector: product
# Under 'fields', we list the data items to be imported. The first level keys
# are the source field names we want to populate (the names to be used as
# sources in the process configuration below). For each field we're importing,
# we provide a label (optional - this is for display in migration tools) and
# an xpath for retrieving that value. It's important to note that this xpath
# is relative to the elements retrieved by item_selector.
fields:
-
name: upc
label: 'Unique product identifier'
selector: upc
-
name: name
label: 'Product name'
selector: name
-
name: description
label: 'Product description'
selector: description
-
name: price
label: 'Product price'
selector: price
# Under 'ids', we identify source fields populated above which will uniquely
# identify each imported item. The 'type' makes sure the migration map table
# uses the proper schema type for stored the IDs.
ids:
upc:
type: integer
process:
# Note that the source field names here (name, description and price) were
# defined by the 'fields' configuration for the source plugin above.
type:
plugin: default_value
default_value: product
title: name
field_upc: upc
field_description: description
field_price: price
sticky:
plugin: default_value
default_value: 0
uid:
plugin: default_value
default_value: 0
destination:
plugin: 'entity:node'
migration_dependencies: { }
dependencies:
enforced:
module:
- migrate_json_example
@@ -0,0 +1,17 @@
langcode: en
status: true
dependencies:
module:
- menu_ui
third_party_settings:
menu_ui:
available_menus:
- main
parent: 'main:'
name: Product
type: product
description: ''
help: ''
new_revision: true
preview_mode: 1
display_submitted: true
@@ -0,0 +1,15 @@
type: module
name: Migrate JSON Example
description: 'Simple JSON Migration example'
package: Examples
# core: 8.x
dependencies:
- drupal:migrate
- migrate_plus:migrate_plus
- migrate_tools:migrate_tools
# Information added by Drupal.org packaging script on 2019-01-07
version: '8.x-4.1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1546879116
@@ -0,0 +1,20 @@
<?php
/**
* @file
* Install, update, and uninstall functions for migrate_json_example.
*/
/**
* Copies the example file to the sites/default/files folder.
*/
function migrate_json_example_install() {
// Create the example file directory and ensure it's writable.
$directory = file_default_scheme() . '://migrate_json_example';
file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
// Copy the example file to example directory.
$module_path = drupal_get_path('module', 'migrate_json_example');
$file_source = $module_path . '/artifacts/products.json';
file_unmanaged_copy($file_source, $directory . '/products.json', FILE_EXISTS_REPLACE);
}
@@ -6,8 +6,8 @@ package: Migration
dependencies:
- drupal:migrate (>=8.3)
# Information added by Drupal.org packaging script on 2018-09-06
version: '8.x-4.0'
# Information added by Drupal.org packaging script on 2019-01-07
version: '8.x-4.1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1536264189
datestamp: 1546879116
@@ -18,8 +18,23 @@ use Drupal\Core\Entity\EntityTypeInterface;
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "weight" = "weight"
* }
* "weight" = "weight",
* "status" = "status"
* },
* config_export = {
* "id",
* "class",
* "field_plugin_method",
* "cck_plugin_method",
* "migration_tags",
* "migration_group",
* "status",
* "label",
* "source",
* "process",
* "destination",
* "migration_dependencies",
* },
* )
*/
class Migration extends ConfigEntityBase implements MigrationInterface {
@@ -19,7 +19,15 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
* entity_keys = {
* "id" = "id",
* "label" = "label"
* }
* },
* config_export = {
* "id",
* "label",
* "description",
* "source_type",
* "module",
* "shared_configuration",
* },
* )
*/
class MigrationGroup extends ConfigEntityBase implements MigrationGroupInterface {
@@ -20,6 +20,10 @@ class MigrationConfigDeriver extends DeriverBase {
$migrations = Migration::loadMultiple();
/** @var \Drupal\migrate_plus\Entity\MigrationInterface $migration */
foreach ($migrations as $id => $migration) {
if (!$migration->status()) {
continue;
}
$this->derivatives[$id] = $migration->toArray();
}
return $this->derivatives;
@@ -131,8 +131,8 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn
$this->entityManager = $entityManager;
$this->selectionPluginManager = $selectionPluginManager;
$pluginIdParts = explode(':', $this->migration->getDestinationPlugin()->getPluginId());
$this->destinationEntityType = empty($pluginIdParts[1]) ?: $pluginIdParts[1];
$this->destinationBundleKey = !$this->destinationEntityType ?: $this->entityManager->getDefinition($this->destinationEntityType)->getKey('bundle');
$this->destinationEntityType = empty($pluginIdParts[1]) ? NULL : $pluginIdParts[1];
$this->destinationBundleKey = $this->destinationEntityType ? $this->entityManager->getDefinition($this->destinationEntityType)->getKey('bundle') : NULL;
}
/**
@@ -223,8 +223,7 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn
break;
default:
throw new MigrateException('Destination field type ' .
$fieldConfig->getType() . 'is not a recognized reference type.');
throw new MigrateException(sprintf('Destination field type %s is not a recognized reference type.', $fieldConfig->getType()));
}
}
}
@@ -262,6 +261,11 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn
$query = $this->entityManager->getStorage($this->lookupEntityType)
->getQuery()
->condition($this->lookupValueKey, $value, $multiple ? 'IN' : NULL);
// Sqlite and possibly others returns data in a non-deterministic order.
// Make it deterministic.
if ($multiple) {
$query->sort($this->lookupValueKey, 'DESC');
}
if ($this->lookupBundleKey) {
$query->condition($this->lookupBundleKey, $this->lookupBundle);
@@ -56,7 +56,7 @@ class Merge extends ProcessPluginBase {
throw new MigrateException(sprintf('Merge process failed for destination property (%s): input is not an array.', $destination_property));
}
$new_value = [];
foreach($value as $i => $item) {
foreach ($value as $i => $item) {
if (!is_array($item)) {
throw new MigrateException(sprintf('Merge process failed for destination property (%s): index (%s) in the source value is not an array that can be merged.', $destination_property, $i));
}
@@ -20,10 +20,10 @@ use Drupal\migrate\Row;
* - value: An single value or array of values against which the source value
* should be compared.
* - not_equals: (optional) If set, skipping occurs when values are not equal.
* - method: What to do if the input value is empty. Possible values:
* - row: Skips the entire row when an empty value is encountered.
* - process: Prevents further processing of the input property when the value
* is empty.
* - method: What to do if the input value equals to value given in
* configuration key value. Possible values:
* - row: Skips the entire row.
* - process: Prevents further processing of the input property
*
* Examples:
*
@@ -32,12 +32,12 @@ use Drupal\migrate\Row;
* type:
* plugin: skip_on_value
* source: content_type
* method: row
* method: process
* value: blog
* @endcode
*
* The above example will skip processing the input property if the content_type
* source field equals "blog".
* The above example will skip further processing of the input property if
* the content_type source field equals "blog".
*
* Example usage with full configuration:
* @code
@@ -59,6 +59,15 @@ use Drupal\migrate\Row;
* All the rules for
* @link http://php.net/manual/function.str-replace.php str_replace @endlink
* apply. This means that you can provide arrays as values.
*
* Multiple values can be matched like this:
* @code
* field_text:
* plugin: str_replace
* source: text
* search: ["AT", "CH", "DK"]
* replace: ["Austria", "Switzerland", "Denmark"]
* @endcode
*/
class StrReplace extends ProcessPluginBase {
@@ -34,7 +34,7 @@ class File extends DataFetcherPluginBase {
* {@inheritdoc}
*/
public function getResponse($url) {
$response = file_get_contents($url);
$response = @file_get_contents($url);
if ($response === FALSE) {
throw new MigrateException('file parser plugin: could not retrieve data from ' . $url);
}
@@ -114,8 +114,8 @@ class Json extends DataParserPluginBase implements ContainerFactoryPluginInterfa
foreach ($field_selectors as $field_selector) {
if (is_array($field_data) && array_key_exists($field_selector, $field_data)) {
$field_data = $field_data[$field_selector];
}
else {
}
else {
$field_data = '';
}
}
@@ -0,0 +1,32 @@
langcode: en
status: true
dependencies: { }
id: fruit_terms
label: Fruit Terms
class: null
field_plugin_method: null
cck_plugin_method: null
migration_tags: { }
migration_group: default
source:
plugin: embedded_data
data_rows:
-
name: Apple
-
name: Banana
-
name: Orange
ids:
name:
type: string
constants:
vocabulary: fruit
process:
name: name
vid: constants/vocabulary
destination:
plugin: entity:taxonomy_term
migration_dependencies:
required: { }
optional: { }
@@ -0,0 +1,9 @@
langcode: en
status: true
dependencies: { }
id: default
label: Default
description: ''
source_type: ''
module: null
shared_configuration: null
@@ -0,0 +1,14 @@
type: module
name: Migrate Plus Test
description: 'Test module to test Migrate Plus.'
package: Testing
# core: 8.x
dependencies:
- drupal:migrate (>=8.3)
- migrate_plus:migrate_plus
# Information added by Drupal.org packaging script on 2019-01-07
version: '8.x-4.1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1546879116
@@ -20,7 +20,10 @@ class LoadTest extends BrowserTestBase {
public static $modules = [
'migrate_plus',
'migrate_example',
'migrate_example_setup',
'migrate_example_advanced',
'migrate_example_advanced_setup',
'migrate_json_example',
];
/**
@@ -2,17 +2,26 @@
namespace Drupal\Tests\migrate_plus\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate_plus\Entity\Migration;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
/**
* Test migration config entity discovery.
*
* @group migrate_plus
*/
class MigrationConfigEntityTest extends KernelTestBase {
class MigrationConfigEntityTest extends MigrateTestBase {
public static $modules = ['migrate', 'migrate_plus'];
public static $modules = [
'migrate',
'migrate_plus',
'migrate_plus_test',
'taxonomy',
'text',
'system',
];
/**
* The plugin manager.
@@ -27,6 +36,9 @@ class MigrationConfigEntityTest extends KernelTestBase {
protected function setUp() {
parent::setUp();
$this->pluginManager = \Drupal::service('plugin.manager.migration');
$this->installConfig('migrate_plus');
$this->installEntitySchema('taxonomy_term');
$this->installSchema('system', ['key_value', 'key_value_expire']);
}
/**
@@ -35,6 +47,7 @@ class MigrationConfigEntityTest extends KernelTestBase {
public function testCacheInvalidation() {
$config = Migration::create([
'id' => 'test',
'status' => TRUE,
'label' => 'Label A',
'migration_tags' => [],
'source' => [],
@@ -57,4 +70,54 @@ class MigrationConfigEntityTest extends KernelTestBase {
$this->assertSame('Label B', $this->pluginManager->getDefinition('test')['label']);
}
/**
* Tests migration status.
*/
public function testMigrationStatus() {
$configs = [
[
'id' => 'test_active',
'status' => TRUE,
'label' => 'Label Active',
'migration_tags' => [],
'source' => [],
'destination' => [],
'migration_dependencies' => [],
],
[
'id' => 'test_inactive',
'status' => FALSE,
'label' => 'Label Inactive',
'migration_tags' => [],
'source' => [],
'destination' => [],
'migration_dependencies' => [],
],
];
foreach ($configs as $config) {
Migration::create($config)->save();
}
$definitions = $this->pluginManager->getDefinitions();
$this->assertCount(1, $definitions);
$this->assertArrayHasKey('test_active', $definitions);
$this->setExpectedException(PluginNotFoundException::class, 'The "test_inactive" plugin does not exist.');
$this->pluginManager->getDefinition('test_inactive');
}
/**
* Tests migration from configuration.
*/
public function testImport() {
$this->installConfig('migrate_plus_test');
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->pluginManager->createInstance('fruit_terms');
$id_map = $migration->getIdMap();
$executable = new MigrateExecutable($migration, $this);
$executable->import();
$this->assertSame(3, $id_map->importedCount());
}
}
@@ -69,10 +69,9 @@ class MigrationGroupTest extends KernelTestBase {
$migration->save();
$expected_config = [
'migration_group' => $group_id,
'label' => 'Unaffected by the group',
'migration_tags' => ['Drupal 7'],
'source' => [
'getMigrationTags' => ['Drupal 7'],
'getSourceConfiguration' => [
'plugin' => 'empty',
'constants' => [
'entity_type' => 'user',
@@ -80,13 +79,13 @@ class MigrationGroupTest extends KernelTestBase {
'cardinality' => '3',
],
],
'destination' => ['plugin' => 'field_storage_config'],
'getDestinationConfiguration' => ['plugin' => 'field_storage_config'],
];
/** @var \Drupal\migrate\Plugin\MigrationInterface $loaded_migration */
/** @var \Drupal\migrate_plus\Plugin\MigrationInterface $loaded_migration */
$loaded_migration = $this->container->get('plugin.manager.migration')
->createInstance('specific_migration');
foreach ($expected_config as $key => $expected_value) {
$actual_value = $loaded_migration->get($key);
foreach ($expected_config as $method => $expected_value) {
$actual_value = call_user_func([$loaded_migration, $method]);
$this->assertEquals($expected_value, $actual_value);
}
}
@@ -5,12 +5,15 @@ namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessageInterface;
use Drupal\node\Entity\NodeType;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
/**
* Tests the migration plugin.
@@ -107,6 +110,18 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
);
// Create a non-reference field.
FieldStorageConfig::create([
'field_name' => 'field_integer',
'type' => 'integer',
'entity_type' => 'node',
])->save();
FieldConfig::create([
'field_name' => 'field_integer',
'entity_type' => 'node',
'bundle' => $this->bundle,
])->save();
$this->migrationPluginManager = \Drupal::service('plugin.manager.migration');
}
@@ -156,14 +171,14 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa
foreach ($valueToCheck as $key => $expectedValue) {
if (empty($expectedValue)) {
if (!$entity->{$property}->isEmpty()) {
$this->assertTrue($entity->{$property}[0]->entity->$key->isEmpty(), "Expected value is empty but field $property.$key is not empty.");
$this->assertTrue($entity->{$property}[0]->entity->{$key}->isEmpty(), "Expected value is empty but field $property.$key is not empty.");
}
else {
$this->assertTrue($entity->{$property}->isEmpty(), "FOOBAR Expected value is empty but field $property is not empty.");
$this->assertTrue($entity->{$property}->isEmpty(), "Expected value is empty but field $property is not empty.");
}
}
elseif ($entity->{$property}->getValue()) {
$this->assertEquals($expectedValue, $entity->{$property}[$valueID]->entity->$key->value);
$this->assertEquals($expectedValue, $entity->get($property)->offsetGet($valueID)->entity->{$key}->value);
}
else {
$this->fail("Expected value: $expectedValue does not exist in $property.");
@@ -177,7 +192,7 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa
foreach ($value as $key => $expectedValue) {
if (empty($expectedValue)) {
if (!$entity->{$property}->isEmpty()) {
$this->assertTrue($entity->{$property}[0]->entity->$key->isEmpty(), "Expected value is empty but field $property.$key is not empty.");
$this->assertTrue($entity->{$property}[0]->entity->{$key}->isEmpty(), "Expected value is empty but field $property.$key is not empty.");
}
else {
$this->assertTrue($entity->{$property}->isEmpty(), "BINBAZ Expected value is empty but field $property is not empty.");
@@ -203,6 +218,99 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa
}
}
/**
* Test lookup without a reference field.
*/
public function testNonReferenceField() {
$values = [
'name' => 'Apples',
'vid' => $this->vocabulary,
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
];
$this->createTestData('taxonomy_term', $values);
// Not enough context is provided for a non reference field, so error out.
$definition = [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => $this->bundle,
],
'title' => 'title',
'field_integer' => [
'plugin' => 'entity_generate',
'source' => 'term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
];
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
$migrationExecutable = (new MigrateExecutable($migration, $this));
$migrationExecutable->import();
$this->assertEquals('Destination field type integer is not a recognized reference type.', $migration->getIdMap()->getMessageIterator()->fetch()->message);
$this->assertSame(1, $migration->getIdMap()->messageCount());
// Enough context is provided so this should work.
$definition = [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'id' => 1,
'title' => 'content item 1',
'term' => 'Apples',
],
],
'ids' => [
'id' => ['type' => 'integer'],
],
],
'process' => [
'id' => 'id',
'type' => [
'plugin' => 'default_value',
'default_value' => $this->bundle,
],
'title' => 'title',
'field_integer' => [
'plugin' => 'entity_generate',
'source' => 'term',
'value_key' => 'name',
'bundle_key' => 'vid',
'bundle' => $this->vocabulary,
'entity_type' => 'taxonomy_term',
],
],
'destination' => [
'plugin' => 'entity:node',
],
];
/** @var \Drupal\migrate\Plugin\Migration $migration */
$migration = $this->migrationPluginManager->createStubMigration($definition);
$migrationExecutable = (new MigrateExecutable($migration, $this));
$migrationExecutable->import();
$this->assertEmpty($migration->getIdMap()->messageCount());
$term = Term::load(1);
$this->assertEquals('Apples', $term->label());
}
/**
* Provides multiple migration definitions for "transform" test.
*/
@@ -775,6 +883,9 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa
* The storage manager to create.
* @param array $values
* The values to use when creating the entity.
*
* @return string|int
* The entity identifier.
*/
private function createTestData($storageName, array $values) {
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
@@ -783,6 +894,7 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa
->getStorage($storageName);
$entity = $storage->create($values);
$entity->save();
return $entity->id();
}
}
@@ -0,0 +1,76 @@
<?php
namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrateDestinationInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the entity_lookup plugin.
*
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate\process\EntityLookup
* @group migrate_plus
*/
class EntityLookupTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
public static $modules = [
'migrate_plus',
'migrate',
'user',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['sequences']);
$this->installEntitySchema('user');
}
/**
* Lookup an entity without bundles on destination key.
*
* Using user entity as destination entity without bundles as example for
* testing.
*
* @covers ::transform
*/
public function testLookupEntityWithoutBundles() {
// Create a user.
$known_user = $this->createUser([], 'lucuma');
// Setup test migration objects.
$migration_prophecy = $this->prophesize(MigrationInterface::class);
$migrate_destination_prophecy = $this->prophesize(MigrateDestinationInterface::class);
$migrate_destination_prophecy->getPluginId()->willReturn('user');
$migrate_destination = $migrate_destination_prophecy->reveal();
$migration_prophecy->getDestinationPlugin()->willReturn($migrate_destination);
$migration_prophecy->getProcess()->willReturn([]);
$migration = $migration_prophecy->reveal();
$configuration = [
'entity_type' => 'user',
'value_key' => 'name',
];
$plugin = \Drupal::service('plugin.manager.migrate.process')
->createInstance('entity_lookup', $configuration, $migration);
$executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
$row = new Row();
// Check the known user is found.
$value = $plugin->transform('lucuma', $executable, $row, 'name');
$this->assertSame($known_user->id(), $value);
// Check an unknown user is not found.
$value = $plugin->transform('orange', $executable, $row, 'name');
$this->assertNull($value);
}
}
@@ -0,0 +1,185 @@
<?php
namespace Drupal\Tests\migrate_plus\Unit\data_fetcher;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\File;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
use org\bovigo\vfs\vfsStream;
/**
* @file
* PHPUnit tests for the Migrate Plus File 'data fetcher' plugin.
*/
/**
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\File
*
* @group migrate_plus
*/
class FileTest extends MigrateTestCase {
/**
* Directory where test data will be created.
*
* @var string
*/
const BASE_DIRECTORY = 'migration_data';
/**
* Minimal migration configuration data.
*
* @var array
*/
private $specificMigrationConfig = [
'source' => 'url',
'data_fetcher_plugin' => 'file',
'data_parser_plugin' => 'json',
'item_selector' => 0,
'fields' => [],
'ids' => [
'id' => [
'type' => 'integer',
],
],
];
/**
* The data fetcher plugin ID being tested.
*
* @var string
*/
private $dataFetcherPluginId = 'file';
/**
* The data fetcher plugin definition.
*
* @var array
*/
private $pluginDefinition = [
'id' => 'file',
'title' => 'File',
];
/**
* Test data to populate a file with.
*
* @var string
*/
private $testData = '[
{
"id": 1,
"name": "Joe Bloggs"
}
]';
/**
* Define virtual dir where we'll be creating files in/fetching files from.
*
* @var \org\bovigo\vfs\vfsStreamDirectory
*/
private $baseDir;
/**
* Set up test environment.
*/
public function setUp() {
$this->baseDir = vfsStream::setup(self::BASE_DIRECTORY);
}
/**
* Test fetching a valid file.
*/
public function testFetchFile() {
$file_name = 'file.json';
$file_path = vfsStream::url(implode(DIRECTORY_SEPARATOR, [self::BASE_DIRECTORY, $file_name]));
$migration_config = $this->specificMigrationConfig + [
'urls' => [$file_path],
];
$plugin = new File(
$migration_config,
$this->dataFetcherPluginId,
$this->pluginDefinition
);
$tree = [
$file_name => $this->testData,
];
vfsStream::create($tree, $this->baseDir);
$expected = json_decode($this->testData, TRUE);
$retrieved = json_decode($plugin->getResponseContent($file_path), TRUE);
$this->assertEquals($expected, $retrieved);
}
/**
* Test fetching multiple valid files.
*/
public function testFetchMultipleFiles() {
$number_of_files = 3;
$file_paths = [];
$file_names = [];
for ($i = 0; $i < $number_of_files; $i++) {
$file_name = 'file_' . $i . '.json';
$file_names[] = $file_name;
$file_paths[] = vfsStream::url(implode(DIRECTORY_SEPARATOR, [self::BASE_DIRECTORY, $file_name]));
}
$migration_config = $this->specificMigrationConfig + [
'urls' => $file_paths,
];
$plugin = new File(
$migration_config,
$this->dataFetcherPluginId,
$this->pluginDefinition
);
for ($i = 0; $i < $number_of_files; $i++) {
$file_name = $file_names[$i];
$file_path = $file_paths[$i];
$tree = [
$file_name => $this->testData,
];
vfsStream::create($tree, $this->baseDir);
$expected = json_decode($this->testData);
$retrieved = json_decode($plugin->getResponseContent($file_path));
$this->assertEquals($expected, $retrieved);
}
}
/**
* Test trying to fetch an unreadable file results in exception.
*/
public function testFetchUnreadableFile() {
$file_name = 'file.json';
$file_path = vfsStream::url(implode(DIRECTORY_SEPARATOR, [self::BASE_DIRECTORY, $file_name]));
$migration_config = $this->specificMigrationConfig + [
'urls' => [$file_path],
];
$plugin = new File(
$migration_config,
$this->dataFetcherPluginId,
$this->pluginDefinition
);
// Create an unreadable file.
vfsStream::newFile($file_name, 0300)
->withContent($this->testData)
->at($this->baseDir);
// Trigger exception trying to read the non-readable file.
$this->setExpectedException(MigrateException::class, 'file parser plugin: could not retrieve data from vfs://migration_data/file.json');
$plugin->getResponseContent($file_path);
}
}
@@ -0,0 +1,242 @@
<?php
namespace Drupal\Tests\migrate_plus\Unit\data_fetcher;
use Drupal\migrate\MigrateException;
use Drupal\migrate_plus\DataFetcherPluginBase;
use Drupal\migrate_plus\Plugin\migrate_plus\authentication\Basic;
use Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\Http;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
/**
* @file
* PHPUnit tests for the Migrate Plus Http 'data fetcher' plugin.
*/
/**
* @coversDefaultClass \Drupal\migrate_plus\Plugin\migrate_plus\data_fetcher\Http
*
* @group migrate_plus
*/
class HttpTest extends MigrateTestCase {
/**
* Minimal migration configuration data.
*
* @var array
*/
private $specificMigrationConfig = [
'source' => 'url',
'urls' => ['http://example.org/http_fetcher_test'],
'data_fetcher_plugin' => 'http',
'data_parser_plugin' => 'json',
'item_selector' => 0,
'authentication' => [
'plugin' => 'basic',
'username' => 'testing',
'password' => 'password',
],
'fields' => [],
'ids' => [
'id' => [
'type' => 'integer',
],
],
];
/**
* The data fetcher plugin ID being tested.
*
* @var string
*/
private $dataFetcherPluginId = 'http';
/**
* The data fetcher plugin definition.
*
* @var array
*/
private $pluginDefinition = [
'id' => 'http',
'title' => 'HTTP',
];
/**
* Test data to validate an HTTP response against.
*
* @var string
*/
private $testData = '
{
"id": 1,
"name": "Joe Bloggs"
}
';
/**
* Mocked up Basic authentication plugin.
*
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $basicAuthenticator = NULL;
/**
* Set up test environment.
*/
public function setUp() {
// Mock up a Basic authentication plugin that will be used in requests.
$basic_authenticator = $this->getMockBuilder(Basic::class)
->disableOriginalConstructor()
->getMock();
$basic_authenticator->method('getAuthenticationOptions')
->will($this->returnValue([
'auth' => [
'username',
'password',
],
]));
$this->basicAuthenticator = $basic_authenticator;
}
/**
* Test 'http' data fetcher (with auth) returns an expected response.
*/
public function testFetchHttpWithAuth() {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[200, 'application/json', $this->testData]], $this->basicAuthenticator);
// The Guzzle mock returns an instance of StreamInterface.
// http://docs.guzzlephp.org/en/latest/psr7.html
$stream = $plugin->getResponseContent($migration_config['urls'][0]);
$body = json_decode((string) $stream, TRUE);
// Compare what we got back from the parser to what we expected to get.
$expected = json_decode($this->testData, TRUE);
$this->assertArrayEquals($expected, $body);
}
/**
* Test 'http' data fetcher (without auth) returns an expected response.
*/
public function testFetchHttpNoAuth() {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
unset($migration_config['authentication']);
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[200, 'application/json', $this->testData]], NULL);
$stream = $plugin->getResponseContent($migration_config['urls'][0]);
$body = json_decode((string) $stream, TRUE);
$expected = json_decode($this->testData, TRUE);
$this->assertArrayEquals($expected, $body);
}
/**
* Test 'http' data fetcher (with auth) dies as expected when auth fails.
*/
public function testFetchHttpAuthFailure() {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[403, 'text/html', 'Forbidden']], $this->basicAuthenticator);
$this->setExpectedException(MigrateException::class, 'Error message: Client error: `GET http://example.org/http_fetcher_test` resulted in a `403 Forbidden');
$plugin->getResponseContent($migration_config['urls'][0]);
}
/**
* Test 'http' data fetcher (with auth) dies as expected when server down.
*/
public function testFetchHttp500Error() {
$migration_config = $this->migrationConfiguration + $this->specificMigrationConfig;
$plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition);
$plugin->mockHttpClient([[500, 'text/html', 'Internal Server Error']], $this->basicAuthenticator);
$this->setExpectedException(MigrateException::class, 'GET http://example.org/http_fetcher_test` resulted in a `500 Internal Server Error');
$plugin->getResponseContent($migration_config['urls'][0]);
}
}
/**
* Test class to mock an HTTP request.
*/
class TestHttp extends Http {
/**
* Mocked authenticator plugin.
*
* @var \PHPUnit_Framework_MockObject_MockObject
*/
public $authenticator = NULL;
/**
* Mock the HttpClient, so we can control the request/response(s) etc.
*
* @param array $responses
* An array of responses (arrays), with each consisting of properties,
* ordered: response code, content-type and response body.
* @param \PHPUnit_Framework_MockObject_MockObject $authenticator
* Mocked authenticator plugin.
*/
public function mockHttpClient(array $responses, \PHPUnit_Framework_MockObject_MockObject $authenticator = NULL) {
// Set mocked authentication plugin to be used for the request auth plugin.
$this->authenticator = $authenticator;
$handler_responses = [];
foreach ($responses as $response) {
$handler_responses[] = new Response(
$response[0],
['Content-Type' => $response[1]],
$response[2]
);
}
$mock = new MockHandler($handler_responses);
$handler = HandlerStack::create($mock);
$this->httpClient = new Client(['handler' => $handler]);
}
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
// Skip calling the Http() constructor (that sets the httpClient instance
// variable via \Drupal which we don't want to do), but keep the call to its
// parent class constructor. @see https://bugs.php.net/bug.php?id=42016
DataFetcherPluginBase::__construct($configuration, $plugin_id, $plugin_definition);
// This is what the parent class is doing, that we need to override.
$this->httpClient = NULL;
}
/**
* Override the parent::getAuthenticationPlugin()
*
* So we can mock the authentication plugin.
*
* @return \PHPUnit_Framework_MockObject_MockObject
* A mocked authentication plugin.
*/
public function getAuthenticationPlugin() {
if (!isset($this->authenticationPlugin)) {
$this->authenticationPlugin = $this->authenticator;
}
return $this->authenticationPlugin;
}
}