upgraded contrib modules
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,15 @@
|
||||
The Migrate Tools module provides tools for running and managing Drupal 8
|
||||
migrations.
|
||||
|
||||
Drush commands supported include:
|
||||
|
||||
* migrate-status - Lists migrations and their status.
|
||||
* migrate-import - Performs import operations.
|
||||
* migrate-rollback - Performs rollback operations.
|
||||
* migrate-stop - Cleanly stops a running operation.
|
||||
* migrate-reset-status - Sets a migration status to Idle if it's gotten stuck.
|
||||
* migrate-messages - Lists any messages associated with a migration import.
|
||||
|
||||
The UI at this point provides a front-end equivalent to the migrate-status and
|
||||
migrate-messages commands. It will be enhanced to allow running the other
|
||||
operations, as well as provide the ability to create and alter migrations.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "drupal/migrate_tools",
|
||||
"description": "Tools to assist in developing and running migrations.",
|
||||
"type": "drupal-module",
|
||||
"require-dev": {
|
||||
"drupal/coder": "^8"
|
||||
},
|
||||
"license": "GPL-2.0+",
|
||||
"require": {},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
migrate_tools.commands:
|
||||
class: \Drupal\migrate_tools\Commands\MigrateToolsCommands
|
||||
arguments: ['@plugin.manager.migration', '@date.formatter', '@entity_type.manager', '@keyvalue']
|
||||
tags:
|
||||
- { name: drush.command }
|
||||
@@ -0,0 +1,524 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Command-line tools to aid performing and developing migrations.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
use Drupal\migrate_tools\MigrateExecutable;
|
||||
use Drupal\migrate_tools\DrushLogMigrateMessage;
|
||||
use Drupal\Core\Datetime\DateFormatter;
|
||||
use Drupal\migrate_plus\Entity\MigrationGroup;
|
||||
|
||||
/**
|
||||
* Implements hook_drush_command().
|
||||
*/
|
||||
function migrate_tools_drush_command() {
|
||||
$items['migrate-status'] = [
|
||||
'description' => 'List all migrations with current status.',
|
||||
'options' => [
|
||||
'group' => 'A comma-separated list of migration groups to list',
|
||||
'tag' => 'Name of the migration tag to list',
|
||||
'names-only' => 'Only return names, not all the details (faster)',
|
||||
],
|
||||
'arguments' => [
|
||||
'migration' => 'Restrict to a comma-separated list of migrations. Optional',
|
||||
],
|
||||
'examples' => [
|
||||
'migrate-status' => 'Retrieve status for all migrations',
|
||||
'migrate-status --group=beer' => 'Retrieve status for all migrations in a given group',
|
||||
'migrate-status --tag=user' => 'Retrieve status for all migrations with a given tag',
|
||||
'migrate-status --group=beer --tag=user' => 'Retrieve status for all migrations in the beer group and with the user tag',
|
||||
'migrate-status beer_term,beer_node' => 'Retrieve status for specific migrations',
|
||||
],
|
||||
'drupal dependencies' => ['migrate_tools'],
|
||||
'aliases' => ['ms'],
|
||||
];
|
||||
|
||||
$items['migrate-import'] = [
|
||||
'description' => 'Perform one or more migration processes.',
|
||||
'options' => [
|
||||
'all' => 'Process all migrations.',
|
||||
'group' => 'A comma-separated list of migration groups to import',
|
||||
'tag' => 'Name of the migration tag to import',
|
||||
'limit' => 'Limit on the number of items to process in each migration',
|
||||
'feedback' => 'Frequency of progress messages, in items processed',
|
||||
'idlist' => 'Comma-separated list of IDs to import',
|
||||
'update' => ' In addition to processing unprocessed items from the source, update previously-imported items with the current data',
|
||||
'force' => 'Force an operation to run, even if all dependencies are not satisfied',
|
||||
'execute-dependencies' => 'Execute all dependent migrations first.',
|
||||
],
|
||||
'arguments' => [
|
||||
'migration' => 'ID of migration(s) to import. Delimit multiple using commas.',
|
||||
],
|
||||
'examples' => [
|
||||
'migrate-import --all' => 'Perform all migrations',
|
||||
'migrate-import --group=beer' => 'Import all migrations in the beer group',
|
||||
'migrate-import --tag=user' => 'Import all migrations with the user tag',
|
||||
'migrate-import --group=beer --tag=user' => 'Import all migrations in the beer group and with the user tag',
|
||||
'migrate-import beer_term,beer_node' => 'Import new terms and nodes',
|
||||
'migrate-import beer_user --limit=2' => 'Import no more than 2 users',
|
||||
'migrate-import beer_user --idlist=5' => 'Import the user record with source ID 5',
|
||||
],
|
||||
'drupal dependencies' => ['migrate_tools'],
|
||||
'aliases' => ['mi', 'mim'],
|
||||
];
|
||||
|
||||
$items['migrate-rollback'] = array(
|
||||
'description' => 'Rollback one or more migrations.',
|
||||
'options' => array(
|
||||
'all' => 'Process all migrations.',
|
||||
'group' => 'A comma-separated list of migration groups to rollback',
|
||||
'tag' => 'ID of the migration tag to rollback',
|
||||
'feedback' => 'Frequency of progress messages, in items processed',
|
||||
),
|
||||
'arguments' => array(
|
||||
'migration' => 'Name of migration(s) to rollback. Delimit multiple using commas.',
|
||||
),
|
||||
'examples' => array(
|
||||
'migrate-rollback --all' => 'Perform all migrations',
|
||||
'migrate-rollback --group=beer' => 'Rollback all migrations in the beer group',
|
||||
'migrate-rollback --tag=user' => 'Rollback all migrations with the user tag',
|
||||
'migrate-rollback --group=beer --tag=user' => 'Rollback all migrations in the beer group and with the user tag',
|
||||
'migrate-rollback beer_term,beer_node' => 'Rollback imported terms and nodes',
|
||||
),
|
||||
'drupal dependencies' => array('migrate_tools'),
|
||||
'aliases' => array('mr'),
|
||||
);
|
||||
|
||||
$items['migrate-stop'] = [
|
||||
'description' => 'Stop an active migration operation.',
|
||||
'arguments' => [
|
||||
'migration' => 'ID of migration to stop',
|
||||
],
|
||||
'drupal dependencies' => ['migrate_tools'],
|
||||
'aliases' => ['mst'],
|
||||
];
|
||||
|
||||
$items['migrate-reset-status'] = [
|
||||
'description' => 'Reset a active migration\'s status to idle.',
|
||||
'arguments' => [
|
||||
'migration' => 'ID of migration to reset',
|
||||
],
|
||||
'drupal dependencies' => ['migrate_tools'],
|
||||
'aliases' => ['mrs'],
|
||||
];
|
||||
|
||||
$items['migrate-messages'] = [
|
||||
'description' => 'View any messages associated with a migration.',
|
||||
'arguments' => [
|
||||
'migration' => 'ID of the migration',
|
||||
],
|
||||
'options' => [
|
||||
'csv' => 'Export messages as a CSV'
|
||||
],
|
||||
'examples' => [
|
||||
'migrate-messages MyNode' => 'Show all messages for the MyNode migration',
|
||||
],
|
||||
'drupal dependencies' => ['migrate_tools'],
|
||||
'aliases' => ['mmsg'],
|
||||
];
|
||||
|
||||
$items['migrate-fields-source'] = [
|
||||
'description' => 'List the fields available for mapping in a source.',
|
||||
'arguments' => [
|
||||
'migration' => 'ID of the migration',
|
||||
],
|
||||
'examples' => [
|
||||
'migrate-fields-source my_node' => 'List fields for the source in the my_node migration',
|
||||
],
|
||||
'drupal dependencies' => ['migrate_tools'],
|
||||
'aliases' => ['mfs'],
|
||||
];
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $migration_names
|
||||
*/
|
||||
function drush_migrate_tools_migrate_status($migration_names = '') {
|
||||
$names_only = drush_get_option('names-only');
|
||||
|
||||
$migrations = drush_migrate_tools_migration_list($migration_names);
|
||||
|
||||
$table = [];
|
||||
// Take it one group at a time, listing the migrations within each group.
|
||||
foreach ($migrations as $group_id => $migration_list) {
|
||||
$group = MigrationGroup::load($group_id);
|
||||
$group_name = !empty($group) ? "{$group->label()} ({$group->id()})" : $group_id;
|
||||
if ($names_only) {
|
||||
$table[] = [
|
||||
dt('Group: @name', array('@name' => $group_name))
|
||||
];
|
||||
}
|
||||
else {
|
||||
$table[] = [
|
||||
dt('Group: @name', array('@name' => $group_name)),
|
||||
dt('Status'),
|
||||
dt('Total'),
|
||||
dt('Imported'),
|
||||
dt('Unprocessed'),
|
||||
dt('Last imported'),
|
||||
];
|
||||
}
|
||||
foreach ($migration_list as $migration_id => $migration) {
|
||||
try {
|
||||
$map = $migration->getIdMap();
|
||||
$imported = $map->importedCount();
|
||||
$source_plugin = $migration->getSourcePlugin();
|
||||
}
|
||||
catch (Exception $e) {
|
||||
drush_log(dt('Failure retrieving information on @migration: @message',
|
||||
['@migration' => $migration_id, '@message' => $e->getMessage()]));
|
||||
continue;
|
||||
}
|
||||
if ($names_only) {
|
||||
$table[] = [$migration_id];
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$source_rows = $source_plugin->count();
|
||||
// -1 indicates uncountable sources.
|
||||
if ($source_rows == -1) {
|
||||
$source_rows = dt('N/A');
|
||||
$unprocessed = dt('N/A');
|
||||
}
|
||||
else {
|
||||
$unprocessed = $source_rows - $map->processedCount();
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
drush_print($e->getMessage());
|
||||
drush_log(dt('Could not retrieve source count from @migration: @message',
|
||||
['@migration' => $migration_id, '@message' => $e->getMessage()]));
|
||||
$source_rows = dt('N/A');
|
||||
$unprocessed = dt('N/A');
|
||||
}
|
||||
|
||||
$status = $migration->getStatusLabel();
|
||||
$migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported');
|
||||
$last_imported = $migrate_last_imported_store->get($migration->id(), FALSE);
|
||||
if ($last_imported) {
|
||||
/** @var DateFormatter $date_formatter */
|
||||
$date_formatter = \Drupal::service('date.formatter');
|
||||
$last_imported = $date_formatter->format($last_imported / 1000,
|
||||
'custom', 'Y-m-d H:i:s');
|
||||
}
|
||||
else {
|
||||
$last_imported = '';
|
||||
}
|
||||
$table[] = [$migration_id, $status, $source_rows, $imported, $unprocessed, $last_imported];
|
||||
}
|
||||
}
|
||||
}
|
||||
drush_print_table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $migration_names
|
||||
*/
|
||||
function drush_migrate_tools_migrate_import($migration_names = '') {
|
||||
$group_names = drush_get_option('group');
|
||||
$tag_names = drush_get_option('tag');
|
||||
$all = drush_get_option('all');
|
||||
|
||||
// Display a depreciation message if "mi" alias is used.
|
||||
$args = drush_get_arguments();
|
||||
if ($args[0] === 'mi') {
|
||||
drush_log('The \'mi\' alias is deprecated and will no longer work with Drush 9. Consider the use of \'mim\' alias instead.', 'warning');
|
||||
}
|
||||
|
||||
$options = [];
|
||||
if (!$all && !$group_names && !$migration_names && !$tag_names) {
|
||||
drush_set_error('MIGRATE_ERROR', dt('You must specify --all, --group, --tag or one or more migration names separated by commas'));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (['limit', 'feedback', 'idlist', 'update', 'force'] as $option) {
|
||||
if (drush_get_option($option)) {
|
||||
$options[$option] = drush_get_option($option);
|
||||
}
|
||||
}
|
||||
|
||||
$migrations = drush_migrate_tools_migration_list($migration_names);
|
||||
if (empty($migrations)) {
|
||||
drush_log(dt('No migrations found.'), 'error');
|
||||
}
|
||||
|
||||
// Take it one group at a time, importing the migrations within each group.
|
||||
foreach ($migrations as $group_id => $migration_list) {
|
||||
array_walk($migration_list, '_drush_migrate_tools_execute_migration', $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single migration. If the --execute-dependencies option was given,
|
||||
* the migration's dependencies will also be executed first.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to execute.
|
||||
* @param string $migration_id
|
||||
* The migration ID (not used, just an artifact of array_walk()).
|
||||
* @param array $options
|
||||
* Additional options for the migration.
|
||||
*/
|
||||
function _drush_migrate_tools_execute_migration(MigrationInterface $migration, $migration_id, array $options = []) {
|
||||
$log = new DrushLogMigrateMessage();
|
||||
|
||||
if (drush_get_option('execute-dependencies')) {
|
||||
if ($required_IDS = $migration->get('requirements')) {
|
||||
$manager = \Drupal::service('plugin.manager.migration');
|
||||
$required_migrations = $manager->createInstances($required_IDS);
|
||||
$dependency_options = array_merge($options, ['is_dependency' => TRUE]);
|
||||
array_walk($required_migrations, __FUNCTION__, $dependency_options);
|
||||
}
|
||||
}
|
||||
if (!empty($options['force'])) {
|
||||
$migration->set('requirements', []);
|
||||
}
|
||||
if (!empty($options['update'])) {
|
||||
$migration->getIdMap()->prepareUpdate();
|
||||
}
|
||||
$executable = new MigrateExecutable($migration, $log, $options);
|
||||
// drush_op() provides --simulate support
|
||||
drush_op(array($executable, 'import'));
|
||||
if ($count = $executable->getFailedCount()) {
|
||||
// Nudge Drush to use a non-zero exit code.
|
||||
drush_set_error('MIGRATE_ERROR', dt('!name Migration - !count failed.', array('!name' => $migration_id, '!count' => $count)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $migration_names
|
||||
*/
|
||||
function drush_migrate_tools_migrate_rollback($migration_names = '') {
|
||||
$group_names = drush_get_option('group');
|
||||
$tag_names = drush_get_option('tag');
|
||||
$all = drush_get_option('all');
|
||||
$options = [];
|
||||
if (!$all && !$group_names && !$migration_names && !$tag_names) {
|
||||
drush_set_error('MIGRATE_ERROR', dt('You must specify --all, --group, --tag, or one or more migration names separated by commas'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (drush_get_option('feedback')) {
|
||||
$options['feedback'] = drush_get_option('feedback');
|
||||
}
|
||||
|
||||
$log = new DrushLogMigrateMessage();
|
||||
|
||||
$migrations = drush_migrate_tools_migration_list($migration_names);
|
||||
if (empty($migrations)) {
|
||||
drush_log(dt('No migrations found.'), 'error');
|
||||
}
|
||||
|
||||
// Take it one group at a time, rolling back the migrations within each group.
|
||||
foreach ($migrations as $group_id => $migration_list) {
|
||||
// Roll back in reverse order.
|
||||
$migration_list = array_reverse($migration_list);
|
||||
foreach ($migration_list as $migration_id => $migration) {
|
||||
$executable = new MigrateExecutable($migration, $log, $options);
|
||||
// drush_op() provides --simulate support.
|
||||
drush_op(array($executable, 'rollback'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $migration_id
|
||||
*/
|
||||
function drush_migrate_tools_migrate_stop($migration_id = '') {
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);
|
||||
if ($migration) {
|
||||
$status = $migration->getStatus();
|
||||
switch ($status) {
|
||||
case MigrationInterface::STATUS_IDLE:
|
||||
drush_log(dt('Migration @id is idle', ['@id' => $migration_id]), 'warning');
|
||||
break;
|
||||
case MigrationInterface::STATUS_DISABLED:
|
||||
drush_log(dt('Migration @id is disabled', ['@id' => $migration_id]), 'warning');
|
||||
break;
|
||||
case MigrationInterface::STATUS_STOPPING:
|
||||
drush_log(dt('Migration @id is already stopping', ['@id' => $migration_id]), 'warning');
|
||||
break;
|
||||
default:
|
||||
$migration->interruptMigration(MigrationInterface::RESULT_STOPPED);
|
||||
drush_log(dt('Migration @id requested to stop', ['@id' => $migration_id]), 'success');
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $migration_id
|
||||
*/
|
||||
function drush_migrate_tools_migrate_reset_status($migration_id = '') {
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);
|
||||
if ($migration) {
|
||||
$status = $migration->getStatus();
|
||||
if ($status == MigrationInterface::STATUS_IDLE) {
|
||||
drush_log(dt('Migration @id is already Idle', ['@id' => $migration_id]), 'warning');
|
||||
}
|
||||
else {
|
||||
$migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
drush_log(dt('Migration @id reset to Idle', ['@id' => $migration_id]), 'status');
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $migration_id
|
||||
*/
|
||||
function drush_migrate_tools_migrate_messages($migration_id) {
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);
|
||||
if ($migration) {
|
||||
$map = $migration->getIdMap();
|
||||
$first = TRUE;
|
||||
$table = [];
|
||||
foreach ($map->getMessageIterator() as $row) {
|
||||
unset($row->msgid);
|
||||
if ($first) {
|
||||
// @todo: Ideally, replace sourceid* with source key names. Or, should
|
||||
// getMessageIterator() do that?
|
||||
foreach ($row as $column => $value) {
|
||||
$table[0][] = $column;
|
||||
}
|
||||
$first = FALSE;
|
||||
}
|
||||
$table[] = (array)$row;
|
||||
}
|
||||
if (empty($table)) {
|
||||
drush_log(dt('No messages for this migration'), 'status');
|
||||
}
|
||||
else {
|
||||
if (drush_get_option('csv')) {
|
||||
foreach ($table as $row) {
|
||||
fputcsv(STDOUT, $row);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$widths = [];
|
||||
foreach ($table[0] as $header) {
|
||||
$widths[] = strlen($header) + 1;
|
||||
}
|
||||
drush_print_table($table, TRUE, $widths);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $migration_id
|
||||
*/
|
||||
function drush_migrate_tools_migrate_fields_source($migration_id) {
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);
|
||||
if ($migration) {
|
||||
$source = $migration->getSourcePlugin();
|
||||
$table = [];
|
||||
foreach ($source->fields() as $machine_name => $description) {
|
||||
$table[] = [strip_tags($description), $machine_name];
|
||||
}
|
||||
drush_print_table($table);
|
||||
}
|
||||
else {
|
||||
drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of active migrations.
|
||||
*
|
||||
* @param string $migration_ids
|
||||
* Comma-separated list of migrations - if present, return only these migrations.
|
||||
*
|
||||
* @return MigrationInterface[][]
|
||||
* An array keyed by migration group, each value containing an array of
|
||||
* migrations or an empty array if no migrations match the input criteria.
|
||||
*/
|
||||
function drush_migrate_tools_migration_list($migration_ids = '') {
|
||||
// Filter keys must match the migration configuration property name.
|
||||
$filter['migration_group'] = drush_get_option('group') ? explode(',', drush_get_option('group')) : [];
|
||||
$filter['migration_tags'] = drush_get_option('tag') ? explode(',', drush_get_option('tag')) : [];
|
||||
|
||||
$manager = \Drupal::service('plugin.manager.migration');
|
||||
$plugins = $manager->createInstances([]);
|
||||
$matched_migrations = [];
|
||||
|
||||
// Get the set of migrations that may be filtered.
|
||||
if (empty($migration_ids)) {
|
||||
$matched_migrations = $plugins;
|
||||
}
|
||||
else {
|
||||
// Get the requested migrations.
|
||||
$migration_ids = explode(',', Unicode::strtolower($migration_ids));
|
||||
foreach ($plugins as $id => $migration) {
|
||||
if (in_array(Unicode::strtolower($id), $migration_ids)) {
|
||||
$matched_migrations[$id] = $migration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not return any migrations which fail to meet requirements.
|
||||
/** @var \Drupal\migrate\Plugin\Migration $migration */
|
||||
foreach ($matched_migrations as $id => $migration) {
|
||||
if ($migration->getSourcePlugin() instanceof RequirementsInterface) {
|
||||
try {
|
||||
$migration->getSourcePlugin()->checkRequirements();
|
||||
}
|
||||
catch (RequirementsException $e) {
|
||||
unset($matched_migrations[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filters the matched migrations if a group or a tag has been input.
|
||||
if (!empty($filter['migration_group']) || !empty($filter['migration_tags'])) {
|
||||
// Get migrations in any of the specified groups and with any of the
|
||||
// specified tags.
|
||||
foreach ($filter as $property => $values) {
|
||||
if (!empty($values)) {
|
||||
$filtered_migrations = [];
|
||||
foreach ($values as $search_value) {
|
||||
foreach ($matched_migrations as $id => $migration) {
|
||||
// Cast to array because migration_tags can be an array.
|
||||
$configured_values = (array) $migration->get($property);
|
||||
$configured_id = (in_array($search_value, $configured_values)) ? $search_value : 'default';
|
||||
if (empty($search_value) || $search_value == $configured_id) {
|
||||
if (empty($migration_ids) || in_array(Unicode::strtolower($id), $migration_ids)) {
|
||||
$filtered_migrations[$id] = $migration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$matched_migrations = $filtered_migrations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the matched migrations by group.
|
||||
if (!empty($matched_migrations)) {
|
||||
foreach ($matched_migrations as $id => $migration) {
|
||||
$configured_group_id = empty($migration->get('migration_group')) ? 'default' : $migration->get('migration_group');
|
||||
$migrations[$configured_group_id][$id] = $migration;
|
||||
}
|
||||
}
|
||||
return isset($migrations) ? $migrations : [];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
type: module
|
||||
name: Migrate Tools
|
||||
description: 'Tools to assist in developing and running migrations.'
|
||||
package: Migration
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:migrate (>=8.3)
|
||||
- migrate_plus:migrate_plus
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-17
|
||||
version: '8.x-4.0-beta1+5-dev'
|
||||
core: '8.x'
|
||||
project: 'migrate_tools'
|
||||
datestamp: 1510918687
|
||||
@@ -0,0 +1,11 @@
|
||||
migrate_tools.add_group_action:
|
||||
route_name: entity.migration_group.add_form
|
||||
title: 'Add migration group'
|
||||
appears_on:
|
||||
- entity.migration_group.list
|
||||
|
||||
#migrate_tools.add_migration_action:
|
||||
# route_name: entity.migration.add_form
|
||||
# title: 'Add migration'
|
||||
# appears_on:
|
||||
# - entity.migration.list
|
||||
@@ -0,0 +1,5 @@
|
||||
migrate_tools.menu:
|
||||
title: Migrations
|
||||
parent: system.admin_structure
|
||||
description: Manage migration processes.
|
||||
route_name: entity.migration_group.list
|
||||
@@ -0,0 +1,41 @@
|
||||
# Component routing definition
|
||||
entity.migration.overview:
|
||||
route_name: entity.migration.overview
|
||||
base_route: entity.migration.overview
|
||||
title: View
|
||||
|
||||
entity.migration.overview_general:
|
||||
title: Overview
|
||||
route_name: entity.migration.overview
|
||||
parent_id: entity.migration.overview
|
||||
entity.migration.overview_source:
|
||||
title: Source
|
||||
route_name: entity.migration.source
|
||||
parent_id: entity.migration.overview
|
||||
weight: 1
|
||||
entity.migration.overview_process:
|
||||
title: Process
|
||||
route_name: entity.migration.process
|
||||
parent_id: entity.migration.overview
|
||||
weight: 2
|
||||
entity.migration.overview_destination:
|
||||
title: Destination
|
||||
route_name: entity.migration.destination
|
||||
parent_id: entity.migration.overview
|
||||
weight: 3
|
||||
|
||||
entity.migration.edit_form:
|
||||
route_name: entity.migration.edit_form
|
||||
base_route: entity.migration.overview
|
||||
title: Edit
|
||||
|
||||
migrate_tools.messages:
|
||||
route_name: migrate_tools.messages
|
||||
base_route: entity.migration.overview
|
||||
title: Messages
|
||||
|
||||
entity.migration.delete_form:
|
||||
route_name: entity.migration.delete_form
|
||||
base_route: entity.migration.overview
|
||||
title: Delete
|
||||
weight: 10
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides tools for implementing and managing migrations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_build().
|
||||
*/
|
||||
function migrate_tools_entity_type_build(array &$entity_types) {
|
||||
// Inject our UI into the general migration and migration group config entities.
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityType[] $entity_types */
|
||||
$entity_types['migration']
|
||||
->set('admin_permission', 'administer migrations')
|
||||
->setHandlerClass('list_builder', 'Drupal\migrate_tools\Controller\MigrationListBuilder')
|
||||
->setFormClass('edit', 'Drupal\migrate_tools\Form\MigrationEditForm')
|
||||
->setFormClass('delete', 'Drupal\migrate_tools\Form\MigrationDeleteForm')
|
||||
->setLinkTemplate('list-form', '/admin/structure/migrate/manage/{migration_group}/migrations');
|
||||
|
||||
$entity_types['migration_group']
|
||||
->set('admin_permission', 'administer migrations')
|
||||
->setHandlerClass('list_builder', 'Drupal\migrate_tools\Controller\MigrationGroupListBuilder')
|
||||
->setFormClass('add', 'Drupal\migrate_tools\Form\MigrationGroupAddForm')
|
||||
->setFormClass('edit', 'Drupal\migrate_tools\Form\MigrationGroupEditForm')
|
||||
->setFormClass('delete', 'Drupal\migrate_tools\Form\MigrationGroupDeleteForm')
|
||||
->setLinkTemplate('edit-form', '/admin/structure/migrate/manage/{migration_group}')
|
||||
->setLinkTemplate('delete-form', '/admin/structure/migrate/manage/{migration_group}/delete');
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
'administer migrations':
|
||||
title: 'Administer migrations'
|
||||
description: Create, edit, and manage migration processed.
|
||||
@@ -0,0 +1,124 @@
|
||||
# This is the router item for listing all migration group entities.
|
||||
entity.migration_group.list:
|
||||
path: '/admin/structure/migrate'
|
||||
defaults:
|
||||
_entity_list: 'migration_group'
|
||||
_title: 'Migrations'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
|
||||
# This is the router item for adding our migration group entity.
|
||||
entity.migration_group.add_form:
|
||||
path: '/admin/structure/migrate/add'
|
||||
defaults:
|
||||
_title: 'Add migration group'
|
||||
_entity_form: migration_group.add
|
||||
requirements:
|
||||
_entity_create_access: migration_group
|
||||
|
||||
# This is the router item for editing our migration group entity.
|
||||
entity.migration_group.edit_form:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}'
|
||||
defaults:
|
||||
_title: 'Edit migration group'
|
||||
_entity_form: migration_group.edit
|
||||
requirements:
|
||||
_entity_access: migration_group.update
|
||||
|
||||
# This is the router item for deleting an instance of our migration group entity.
|
||||
entity.migration_group.delete_form:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/delete'
|
||||
defaults:
|
||||
_title: 'Delete migration group'
|
||||
_entity_form: migration_group.delete
|
||||
requirements:
|
||||
_entity_access: migration_group.delete
|
||||
|
||||
# This is the router item for listing all migration entities.
|
||||
entity.migration.list:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations'
|
||||
defaults:
|
||||
_entity_list: 'migration'
|
||||
_title: 'Migrations'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
|
||||
# This is the router item for adding our migration entity.
|
||||
#entity.migration.add_form:
|
||||
# path: '/admin/structure/migrate/manage/{migration_group}/migrations/add'
|
||||
# defaults:
|
||||
# _title: 'Add migration'
|
||||
# _entity_form: migration.add
|
||||
# requirements:
|
||||
# _entity_create_access: migration
|
||||
|
||||
# This is the router item for viewing our migration entity.
|
||||
entity.migration.overview:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate_tools\Controller\MigrationController::overview'
|
||||
_title: 'Migration overview'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
entity.migration.source:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/source'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate_tools\Controller\MigrationController::source'
|
||||
_title: 'Source'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
entity.migration.process:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/process'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate_tools\Controller\MigrationController::process'
|
||||
_title: 'Process'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
entity.migration.process.run:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/process/run'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate_tools\Controller\MigrationController::run'
|
||||
_title: 'Run'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
entity.migration.destination:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/destination'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate_tools\Controller\MigrationController::destination'
|
||||
_title: 'Destination'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
|
||||
# This is the router item for editing our migration entity.
|
||||
entity.migration.edit_form:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/edit'
|
||||
defaults:
|
||||
_title: 'Edit migration'
|
||||
_entity_form: migration.edit
|
||||
requirements:
|
||||
_entity_access: migration.update
|
||||
|
||||
# This is the router item for deleting an instance of our migration entity.
|
||||
entity.migration.delete_form:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/delete'
|
||||
defaults:
|
||||
_title: 'Delete migration'
|
||||
_entity_form: migration.delete
|
||||
requirements:
|
||||
_entity_access: migration.delete
|
||||
|
||||
migrate_tools.messages:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/messages'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate_tools\Controller\MessageController::overview'
|
||||
_title: 'Messages'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
|
||||
migrate_tools.execute:
|
||||
path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/execute'
|
||||
defaults:
|
||||
_form: '\Drupal\migrate_tools\Form\MigrationExecuteForm'
|
||||
_title: 'Execute migration'
|
||||
requirements:
|
||||
_permission: 'administer migrations'
|
||||
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="drupal_core">
|
||||
<description>PHP CodeSniffer configuration</description>
|
||||
<file>.</file>
|
||||
<arg name="extensions" value="install,module,php"/>
|
||||
|
||||
<!--Exclude third party code.-->
|
||||
<exclude-pattern>./vendor/*</exclude-pattern>
|
||||
|
||||
<!-- Only include specific sniffs that pass. This ensures that, if new sniffs are added, HEAD does not fail.-->
|
||||
<!-- Drupal sniffs -->
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Classes/ClassCreateInstanceSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Classes/ClassDeclarationSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Classes/FullyQualifiedNamespaceSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Classes/UnusedUseStatementSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Classes/UseLeadingBackslashSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/CSS/ClassDefinitionNameSpacingSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/CSS/ColourDefinitionSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/ClassCommentSniff.php">
|
||||
<exclude name="Drupal.Commenting.ClassComment.Missing"/>
|
||||
</rule>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/DocCommentSniff.php">
|
||||
<!-- Sniff for these errors: SpacingAfterTagGroup, WrongEnd, SpacingBetween,
|
||||
ContentAfterOpen, SpacingBeforeShort, TagValueIndent, ShortStartSpace,
|
||||
SpacingAfter -->
|
||||
<exclude name="Drupal.Commenting.DocComment.LongNotCapital"/>
|
||||
<!-- ParamNotFirst still not decided for PHPUnit-based tests.
|
||||
@see https://www.drupal.org/node/2253915 -->
|
||||
<exclude name="Drupal.Commenting.DocComment.ParamNotFirst"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.SpacingBeforeTags"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.LongFullStop"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.ShortNotCapital"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.ShortFullStop"/>
|
||||
<!-- TagsNotGrouped and ParamGroup have false-positives.
|
||||
@see https://www.drupal.org/node/2060925 -->
|
||||
<exclude name="Drupal.Commenting.DocComment.TagsNotGrouped"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.ParamGroup"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.ShortSingleLine"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.TagGroupSpacing"/>
|
||||
<exclude name="Drupal.Commenting.DocComment.MissingShort"/>
|
||||
</rule>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/DocCommentStarSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/FileCommentSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Commenting/FunctionCommentSniff.php">
|
||||
<exclude name="Drupal.Commenting.FunctionComment.IncorrectTypeHint"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.InvalidNoReturn"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.InvalidReturnNotVoid"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.InvalidTypeHint"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.Missing"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.MissingFile"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.MissingParamComment"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.MissingParamType"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.MissingReturnComment"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.MissingReturnType"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.ParamCommentFullStop"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.ParamCommentIndentation"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.ParamMissingDefinition"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.ParamNameNoMatch"/>
|
||||
<exclude name="Drupal.Commenting.FunctionComment.TypeHintMissing"/>
|
||||
</rule>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/ControlStructures/ElseIfSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/ControlStructures/ControlSignatureSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Files/EndFileNewlineSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Files/TxtFileLineLengthSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Formatting/MultiLineAssignmentSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Formatting/SpaceInlineIfSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Formatting/SpaceUnaryOperatorSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Functions/DiscouragedFunctionsSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Functions/FunctionDeclarationSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/InfoFiles/AutoAddedKeysSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/InfoFiles/ClassFilesSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/InfoFiles/DuplicateEntrySniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/InfoFiles/RequiredSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/NamingConventions/ValidVariableNameSniff.php">
|
||||
<!-- Sniff for: LowerStart -->
|
||||
<exclude name="Drupal.NamingConventions.ValidVariableName.LowerCamelName"/>
|
||||
</rule>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Scope/MethodScopeSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/EmptyInstallSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/FunctionTSniff.php">
|
||||
<exclude name="Drupal.Semantics.FunctionT.BackslashSingleQuote"/>
|
||||
<exclude name="Drupal.Semantics.FunctionT.NotLiteralString"/>
|
||||
<exclude name="Drupal.Semantics.FunctionT.ConcatString"/>
|
||||
</rule>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/FunctionWatchdogSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/InstallHooksSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/LStringTranslatableSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/PregSecuritySniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/TInHookMenuSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/Semantics/TInHookSchemaSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/CommaSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/EmptyLinesSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/ObjectOperatorIndentSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/OpenTagNewlineSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/OperatorSpacingSniff.php"/>
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/ScopeIndentSniff.php"/>
|
||||
|
||||
<!-- Drupal Practice sniffs -->
|
||||
<rule ref="vendor/drupal/coder/coder_sniffer/DrupalPractice/Sniffs/Commenting/ExpectedExceptionSniff.php"/>
|
||||
|
||||
<!-- Generic sniffs -->
|
||||
<rule ref="Generic.Files.LineEndings"/>
|
||||
<rule ref="Generic.Functions.FunctionCallArgumentSpacing"/>
|
||||
<rule ref="Generic.NamingConventions.ConstructorName" />
|
||||
<rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
|
||||
<rule ref="Generic.PHP.DeprecatedFunctions"/>
|
||||
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
|
||||
<rule ref="Generic.PHP.UpperCaseConstant"/>
|
||||
<rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
|
||||
<rule ref="Generic.Arrays.DisallowLongArraySyntax" />
|
||||
|
||||
<!-- PSR-2 sniffs -->
|
||||
<rule ref="PSR2.Classes.PropertyDeclaration">
|
||||
<exclude name="PSR2.Classes.PropertyDeclaration.Underscore"/>
|
||||
</rule>
|
||||
|
||||
<!-- Squiz sniffs -->
|
||||
<rule ref="Squiz.Strings.ConcatenationSpacing">
|
||||
<properties>
|
||||
<property name="spacing" value="1"/>
|
||||
<property name="ignoreNewlines" value="true"/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
+741
@@ -0,0 +1,741 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Commands;
|
||||
|
||||
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Datetime\DateFormatter;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManager;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
use Drupal\migrate_tools\Drush9LogMigrateMessage;
|
||||
use Drupal\migrate_tools\DrushLogMigrateMessage;
|
||||
use Drupal\migrate_tools\MigrateExecutable;
|
||||
use Drush\Commands\DrushCommands;
|
||||
use Drush\Drush;
|
||||
|
||||
/**
|
||||
* Migrate Tools drush commands.
|
||||
*/
|
||||
class MigrateToolsCommands extends DrushCommands {
|
||||
|
||||
/**
|
||||
* Migration plugin manager service.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManager
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* Date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatter
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Key-value store service.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
|
||||
*/
|
||||
protected $keyValue;
|
||||
|
||||
/**
|
||||
* Migrate message logger.
|
||||
*
|
||||
* @var \Drupal\migrate_tools\Drush9LogMigrateMessage
|
||||
*/
|
||||
protected $migrateMessage;
|
||||
|
||||
/**
|
||||
* MigrateToolsCommands constructor.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManager $migrationPluginManager
|
||||
* Migration Plugin Manager service.
|
||||
* @param \Drupal\Core\Datetime\DateFormatter $dateFormatter
|
||||
* Date formatter service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* Entity type manager service.
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $keyValue
|
||||
* Key-value store service.
|
||||
*/
|
||||
public function __construct(
|
||||
MigrationPluginManager $migrationPluginManager,
|
||||
DateFormatter $dateFormatter,
|
||||
EntityTypeManagerInterface $entityTypeManager,
|
||||
KeyValueFactoryInterface $keyValue
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->migrationPluginManager = $migrationPluginManager;
|
||||
$this->dateFormatter = $dateFormatter;
|
||||
$this->entityTypeManager = $entityTypeManager;
|
||||
$this->keyValue = $keyValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all migrations with current status.
|
||||
*
|
||||
* @param string $migration_names
|
||||
* Restrict to a comma-separated list of migrations (Optional).
|
||||
* @param array $options
|
||||
* Additional options for the command.
|
||||
*
|
||||
* @command migrate:status
|
||||
*
|
||||
* @option group A comma-separated list of migration groups to list
|
||||
* @option tag Name of the migration tag to list
|
||||
* @option names-only Only return names, not all the details (faster)
|
||||
*
|
||||
* @usage migrate:status
|
||||
* Retrieve status for all migrations
|
||||
* @usage migrate:status --group=beer
|
||||
* Retrieve status for all migrations in a given group
|
||||
* @usage migrate:status --tag=user
|
||||
* Retrieve status for all migrations with a given tag
|
||||
* @usage migrate:status --group=beer --tag=user
|
||||
* Retrieve status for all migrations in the beer group
|
||||
* and with the user tag.
|
||||
* @usage migrate:status beer_term,beer_node
|
||||
* Retrieve status for specific migrations
|
||||
*
|
||||
* @validate-module-enabled migrate_tools
|
||||
*
|
||||
* @aliases ms, migrate-status
|
||||
*
|
||||
* @field-labels
|
||||
* group: Group
|
||||
* id: Migration ID
|
||||
* status: Status
|
||||
* total: Total
|
||||
* imported: Imported
|
||||
* unprocessed: Unprocessed
|
||||
* last_imported: Last Imported
|
||||
* @default-fields group,id,status,total,imported,unprocessed,last_imported
|
||||
*
|
||||
* @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
|
||||
* Migrations status formatted as table.
|
||||
*/
|
||||
public function status(
|
||||
$migration_names = '',
|
||||
array $options = [
|
||||
'group' => NULL,
|
||||
'tag' => NULL,
|
||||
'names-only' => NULL,
|
||||
]
|
||||
) {
|
||||
$names_only = $options['names-only'];
|
||||
|
||||
$migrations = $this->migrationsList($migration_names, $options);
|
||||
|
||||
$table = [];
|
||||
// Take it one group at a time, listing the migrations within each group.
|
||||
foreach ($migrations as $group_id => $migration_list) {
|
||||
/** @var \Drupal\migrate_plus\Entity\MigrationGroup $group */
|
||||
$group = $this->entityTypeManager->getStorage('migration_group')->load($group_id);
|
||||
$group_name = !empty($group) ? "{$group->label()} ({$group->id()})" : $group_id;
|
||||
|
||||
foreach ($migration_list as $migration_id => $migration) {
|
||||
if ($names_only) {
|
||||
$table[] = [
|
||||
'group' => dt('Group: @name', ['@name' => $group_name]),
|
||||
'id' => $migration_id,
|
||||
];
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$map = $migration->getIdMap();
|
||||
$imported = $map->importedCount();
|
||||
$source_plugin = $migration->getSourcePlugin();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->logger()->error(
|
||||
dt(
|
||||
'Failure retrieving information on @migration: @message',
|
||||
['@migration' => $migration_id, '@message' => $e->getMessage()]
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$source_rows = $source_plugin->count();
|
||||
// -1 indicates uncountable sources.
|
||||
if ($source_rows == -1) {
|
||||
$source_rows = dt('N/A');
|
||||
$unprocessed = dt('N/A');
|
||||
}
|
||||
else {
|
||||
$unprocessed = $source_rows - $map->processedCount();
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->logger()->error(
|
||||
dt(
|
||||
'Could not retrieve source count from @migration: @message',
|
||||
['@migration' => $migration_id, '@message' => $e->getMessage()]
|
||||
)
|
||||
);
|
||||
$source_rows = dt('N/A');
|
||||
$unprocessed = dt('N/A');
|
||||
}
|
||||
|
||||
$status = $migration->getStatusLabel();
|
||||
$migrate_last_imported_store = $this->keyValue->get(
|
||||
'migrate_last_imported'
|
||||
);
|
||||
$last_imported = $migrate_last_imported_store->get(
|
||||
$migration->id(),
|
||||
FALSE
|
||||
);
|
||||
if ($last_imported) {
|
||||
$last_imported = $this->dateFormatter->format(
|
||||
$last_imported / 1000,
|
||||
'custom',
|
||||
'Y-m-d H:i:s'
|
||||
);
|
||||
}
|
||||
else {
|
||||
$last_imported = '';
|
||||
}
|
||||
$table[] = [
|
||||
'group' => $group_name,
|
||||
'id' => $migration_id,
|
||||
'status' => $status,
|
||||
'total' => $source_rows,
|
||||
'imported' => $imported,
|
||||
'unprocessed' => $unprocessed,
|
||||
'last_imported' => $last_imported,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Add empty row to separate groups, for readability.
|
||||
end($migrations);
|
||||
if ($group_id !== key($migrations)) {
|
||||
$table[] = [];
|
||||
}
|
||||
}
|
||||
|
||||
return new RowsOfFields($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform one or more migration processes.
|
||||
*
|
||||
* @param string $migration_names
|
||||
* ID of migration(s) to import. Delimit multiple using commas.
|
||||
* @param array $options
|
||||
* Additional options for the command.
|
||||
*
|
||||
* @command migrate:import
|
||||
*
|
||||
* @option all Process all migrations.
|
||||
* @option group A comma-separated list of migration groups to import
|
||||
* @option tag Name of the migration tag to import
|
||||
* @option limit Limit on the number of items to process in each migration
|
||||
* @option feedback Frequency of progress messages, in items processed
|
||||
* @option idlist Comma-separated list of IDs to import
|
||||
* @option update In addition to processing unprocessed items from the
|
||||
* source, update previously-imported items with the current data
|
||||
* @option force Force an operation to run, even if all dependencies are not
|
||||
* satisfied
|
||||
* @option execute-dependencies Execute all dependent migrations first.
|
||||
*
|
||||
* @usage migrate:import --all
|
||||
* Perform all migrations
|
||||
* @usage migrate:import --group=beer
|
||||
* Import all migrations in the beer group
|
||||
* @usage migrate:import --tag=user
|
||||
* Import all migrations with the user tag
|
||||
* @usage migrate:import --group=beer --tag=user
|
||||
* Import all migrations in the beer group and with the user tag
|
||||
* @usage migrate:import beer_term,beer_node
|
||||
* Import new terms and nodes
|
||||
* @usage migrate:import beer_user --limit=2
|
||||
* Import no more than 2 users
|
||||
* @usage migrate:import beer_user --idlist=5
|
||||
* Import the user record with source ID 5
|
||||
*
|
||||
* @validate-module-enabled migrate_tools
|
||||
*
|
||||
* @aliases mim, migrate-import
|
||||
*
|
||||
* @throws \Exception
|
||||
* If there are not enough parameters to the command.
|
||||
*/
|
||||
public function import(
|
||||
$migration_names = '',
|
||||
array $options = [
|
||||
'all' => NULL,
|
||||
'group' => NULL,
|
||||
'tag' => NULL,
|
||||
'limit' => NULL,
|
||||
'feedback' => NULL,
|
||||
'idlist' => NULL,
|
||||
'update' => NULL,
|
||||
'force' => NULL,
|
||||
'execute-dependencies' => NULL,
|
||||
]
|
||||
) {
|
||||
$group_names = $options['group'];
|
||||
$tag_names = $options['tag'];
|
||||
$all = $options['all'];
|
||||
$additional_options = [];
|
||||
if (!$all && !$group_names && !$migration_names && !$tag_names) {
|
||||
throw new \Exception(dt('You must specify --all, --group, --tag or one or more migration names separated by commas'));
|
||||
}
|
||||
|
||||
foreach (['limit', 'feedback', 'idlist', 'update', 'force'] as $option) {
|
||||
if ($options[$option]) {
|
||||
$additional_options[$option] = $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
$migrations = $this->migrationsList($migration_names, $options);
|
||||
if (empty($migrations)) {
|
||||
$this->logger->error(dt('No migrations found.'));
|
||||
}
|
||||
|
||||
// Take it one group at a time, importing the migrations within each group.
|
||||
foreach ($migrations as $group_id => $migration_list) {
|
||||
array_walk(
|
||||
$migration_list,
|
||||
[$this, 'executeMigration'],
|
||||
$additional_options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback one or more migrations.
|
||||
*
|
||||
* @param string $migration_names
|
||||
* Name of migration(s) to rollback. Delimit multiple using commas.
|
||||
* @param array $options
|
||||
* Additional options for the command.
|
||||
*
|
||||
* @command migrate:rollback
|
||||
*
|
||||
* @option all Process all migrations.
|
||||
* @option group A comma-separated list of migration groups to rollback
|
||||
* @option tag ID of the migration tag to rollback
|
||||
* @option feedback Frequency of progress messages, in items processed
|
||||
*
|
||||
* @usage migrate:rollback --all
|
||||
* Perform all migrations
|
||||
* @usage migrate:rollback --group=beer
|
||||
* Rollback all migrations in the beer group
|
||||
* @usage migrate:rollback --tag=user
|
||||
* Rollback all migrations with the user tag
|
||||
* @usage migrate:rollback --group=beer --tag=user
|
||||
* Rollback all migrations in the beer group and with the user tag
|
||||
* @usage migrate:rollback beer_term,beer_node
|
||||
* Rollback imported terms and nodes
|
||||
* @validate-module-enabled migrate_tools
|
||||
*
|
||||
* @aliases mr, migrate-rollback
|
||||
*
|
||||
* @throws \Exception
|
||||
* If there are not enough parameters to the command.
|
||||
*/
|
||||
public function rollback(
|
||||
$migration_names = '',
|
||||
array $options = [
|
||||
'all' => NULL,
|
||||
'group' => NULL,
|
||||
'tag' => NULL,
|
||||
'feedback' => NULL,
|
||||
]
|
||||
) {
|
||||
$group_names = $options['group'];
|
||||
$tag_names = $options['tag'];
|
||||
$all = $options['all'];
|
||||
$additional_options = [];
|
||||
if (!$all && !$group_names && !$migration_names && !$tag_names) {
|
||||
throw new \Exception(dt('You must specify --all, --group, --tag, or one or more migration names separated by commas'));
|
||||
}
|
||||
|
||||
if ($options['feedback']) {
|
||||
$additional_options['feedback'] = $options['feedback'];
|
||||
}
|
||||
|
||||
$migrations = $this->migrationsList($migration_names, $options);
|
||||
if (empty($migrations)) {
|
||||
$this->logger()->error(dt('No migrations found.'));
|
||||
}
|
||||
|
||||
// Take it one group at a time,
|
||||
// rolling back the migrations within each group.
|
||||
foreach ($migrations as $group_id => $migration_list) {
|
||||
// Roll back in reverse order.
|
||||
$migration_list = array_reverse($migration_list);
|
||||
foreach ($migration_list as $migration_id => $migration) {
|
||||
$executable = new MigrateExecutable(
|
||||
$migration,
|
||||
$this->getMigrateMessage(),
|
||||
$additional_options
|
||||
);
|
||||
// drush_op() provides --simulate support.
|
||||
drush_op([$executable, 'rollback']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop an active migration operation.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* ID of migration to stop.
|
||||
*
|
||||
* @command migrate:stop
|
||||
*
|
||||
* @validate-module-enabled migrate_tools
|
||||
* @aliases mst, migrate-stop
|
||||
*/
|
||||
public function stop($migration_id = '') {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance(
|
||||
$migration_id
|
||||
);
|
||||
if ($migration) {
|
||||
$status = $migration->getStatus();
|
||||
switch ($status) {
|
||||
case MigrationInterface::STATUS_IDLE:
|
||||
$this->logger()->warning(
|
||||
dt('Migration @id is idle', ['@id' => $migration_id])
|
||||
);
|
||||
break;
|
||||
|
||||
case MigrationInterface::STATUS_DISABLED:
|
||||
$this->logger()->warning(
|
||||
dt('Migration @id is disabled', ['@id' => $migration_id])
|
||||
);
|
||||
break;
|
||||
|
||||
case MigrationInterface::STATUS_STOPPING:
|
||||
$this->logger()->warning(
|
||||
dt('Migration @id is already stopping', ['@id' => $migration_id])
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
$migration->interruptMigration(MigrationInterface::RESULT_STOPPED);
|
||||
$this->logger()->notice(
|
||||
dt('Migration @id requested to stop', ['@id' => $migration_id])
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->logger()->error(
|
||||
dt('Migration @id does not exist', ['@id' => $migration_id])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a active migration's status to idle.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* ID of migration to reset.
|
||||
*
|
||||
* @command migrate:reset-status
|
||||
*
|
||||
* @validate-module-enabled migrate_tools
|
||||
* @aliases mrs, migrate-reset-status
|
||||
*/
|
||||
public function resetStatus($migration_id = '') {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance(
|
||||
$migration_id
|
||||
);
|
||||
if ($migration) {
|
||||
$status = $migration->getStatus();
|
||||
if ($status == MigrationInterface::STATUS_IDLE) {
|
||||
$this->logger()->warning(
|
||||
dt('Migration @id is already Idle', ['@id' => $migration_id])
|
||||
);
|
||||
}
|
||||
else {
|
||||
$migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
$this->logger()->notice(
|
||||
dt('Migration @id reset to Idle', ['@id' => $migration_id])
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->logger()->error(
|
||||
dt('Migration @id does not exist', ['@id' => $migration_id])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View any messages associated with a migration.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* ID of the migration.
|
||||
* @param array $options
|
||||
* Additional options for the command.
|
||||
*
|
||||
* @command migrate:messages
|
||||
*
|
||||
* @option csv Export messages as a CSV
|
||||
*
|
||||
* @usage migrate:messages MyNode
|
||||
* Show all messages for the MyNode migration
|
||||
*
|
||||
* @validate-module-enabled migrate_tools
|
||||
*
|
||||
* @aliases mmsg,migrate-messages
|
||||
*/
|
||||
public function messages($migration_id, array $options = ['csv' => NULL]) {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance(
|
||||
$migration_id
|
||||
);
|
||||
if ($migration) {
|
||||
$map = $migration->getIdMap();
|
||||
$first = TRUE;
|
||||
$table = [];
|
||||
foreach ($map->getMessageIterator() as $row) {
|
||||
unset($row->msgid);
|
||||
if ($first) {
|
||||
// @todo: Ideally, replace sourceid* with source key names. Or, should
|
||||
// getMessageIterator() do that?
|
||||
foreach ($row as $column => $value) {
|
||||
$table[0][] = $column;
|
||||
}
|
||||
$first = FALSE;
|
||||
}
|
||||
$table[] = (array) $row;
|
||||
}
|
||||
if (empty($table)) {
|
||||
$this->logger()->notice(dt('No messages for this migration'));
|
||||
}
|
||||
else {
|
||||
if ($options['csv']) {
|
||||
foreach ($table as $row) {
|
||||
fputcsv(STDOUT, $row);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$widths = [];
|
||||
foreach ($table[0] as $header) {
|
||||
$widths[] = strlen($header) + 1;
|
||||
}
|
||||
|
||||
drush_print_table($table);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->logger()->error(
|
||||
dt('Migration @id does not exist', ['@id' => $migration_id])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List the fields available for mapping in a source.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* ID of the migration.
|
||||
*
|
||||
* @command migrate:fields-source
|
||||
*
|
||||
* @usage migrate:fields-source my_node
|
||||
* List fields for the source in the my_node migration
|
||||
*
|
||||
* @validate-module-enabled migrate_tools
|
||||
*
|
||||
* @aliases mfs, migrate-fields-source
|
||||
*/
|
||||
public function fieldsSource($migration_id) {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance(
|
||||
$migration_id
|
||||
);
|
||||
if ($migration) {
|
||||
$source = $migration->getSourcePlugin();
|
||||
$table = [];
|
||||
foreach ($source->fields() as $machine_name => $description) {
|
||||
$table[] = [strip_tags($description), $machine_name];
|
||||
}
|
||||
drush_print_table($table);
|
||||
}
|
||||
else {
|
||||
$this->logger()->error(
|
||||
dt('Migration @id does not exist', ['@id' => $migration_id])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of active migrations.
|
||||
*
|
||||
* @param string $migration_ids
|
||||
* Comma-separated list of migrations -
|
||||
* if present, return only these migrations.
|
||||
* @param array $options
|
||||
* Command options.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface[][]
|
||||
* An array keyed by migration group, each value containing an array of
|
||||
* migrations or an empty array if no migrations match the input criteria.
|
||||
*/
|
||||
protected function migrationsList($migration_ids = '', array $options = []) {
|
||||
// Filter keys must match the migration configuration property name.
|
||||
$filter['migration_group'] = $options['group'] ? explode(
|
||||
',',
|
||||
$options['group']
|
||||
) : [];
|
||||
$filter['migration_tags'] = $options['tag'] ? explode(
|
||||
',',
|
||||
$options['tag']
|
||||
) : [];
|
||||
|
||||
$manager = $this->migrationPluginManager;
|
||||
$plugins = $manager->createInstances([]);
|
||||
$matched_migrations = [];
|
||||
|
||||
// Get the set of migrations that may be filtered.
|
||||
if (empty($migration_ids)) {
|
||||
$matched_migrations = $plugins;
|
||||
}
|
||||
else {
|
||||
// Get the requested migrations.
|
||||
$migration_ids = explode(',', Unicode::strtolower($migration_ids));
|
||||
foreach ($plugins as $id => $migration) {
|
||||
if (in_array(Unicode::strtolower($id), $migration_ids)) {
|
||||
$matched_migrations[$id] = $migration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not return any migrations which fail to meet requirements.
|
||||
/** @var \Drupal\migrate\Plugin\Migration $migration */
|
||||
foreach ($matched_migrations as $id => $migration) {
|
||||
if ($migration->getSourcePlugin() instanceof RequirementsInterface) {
|
||||
try {
|
||||
$migration->getSourcePlugin()->checkRequirements();
|
||||
}
|
||||
catch (RequirementsException $e) {
|
||||
unset($matched_migrations[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filters the matched migrations if a group or a tag has been input.
|
||||
if (!empty($filter['migration_group']) || !empty($filter['migration_tags'])) {
|
||||
// Get migrations in any of the specified groups and with any of the
|
||||
// specified tags.
|
||||
foreach ($filter as $property => $values) {
|
||||
if (!empty($values)) {
|
||||
$filtered_migrations = [];
|
||||
foreach ($values as $search_value) {
|
||||
foreach ($matched_migrations as $id => $migration) {
|
||||
// Cast to array because migration_tags can be an array.
|
||||
$configured_values = (array) $migration->get($property);
|
||||
$configured_id = (in_array(
|
||||
$search_value,
|
||||
$configured_values
|
||||
)) ? $search_value : 'default';
|
||||
if (empty($search_value) || $search_value == $configured_id) {
|
||||
if (empty($migration_ids) || in_array(
|
||||
Unicode::strtolower($id),
|
||||
$migration_ids
|
||||
)) {
|
||||
$filtered_migrations[$id] = $migration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$matched_migrations = $filtered_migrations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the matched migrations by group.
|
||||
if (!empty($matched_migrations)) {
|
||||
foreach ($matched_migrations as $id => $migration) {
|
||||
$configured_group_id = empty($migration->get('migration_group')) ? 'default' : $migration->get('migration_group');
|
||||
$migrations[$configured_group_id][$id] = $migration;
|
||||
}
|
||||
}
|
||||
return isset($migrations) ? $migrations : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single migration.
|
||||
*
|
||||
* If the --execute-dependencies option was given,
|
||||
* the migration's dependencies will also be executed first.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to execute.
|
||||
* @param string $migration_id
|
||||
* The migration ID (not used, just an artifact of array_walk()).
|
||||
* @param array $options
|
||||
* Additional options of the command.
|
||||
*
|
||||
* @throws \Exception
|
||||
* If some migrations failed during execution.
|
||||
*/
|
||||
protected function executeMigration(
|
||||
MigrationInterface $migration,
|
||||
$migration_id,
|
||||
array $options = []
|
||||
) {
|
||||
if (isset($options['execute-dependencies'])) {
|
||||
if ($required_IDS = $migration->get('requirements')) {
|
||||
$manager = $this->migrationPluginManager;
|
||||
$required_migrations = $manager->createInstances($required_IDS);
|
||||
$dependency_options = array_merge($options, ['is_dependency' => TRUE]);
|
||||
array_walk($required_migrations, __FUNCTION__, $dependency_options);
|
||||
}
|
||||
}
|
||||
if (!empty($options['force'])) {
|
||||
$migration->set('requirements', []);
|
||||
}
|
||||
if (!empty($options['update'])) {
|
||||
$migration->getIdMap()->prepareUpdate();
|
||||
}
|
||||
$executable = new MigrateExecutable($migration, $this->getMigrateMessage(), $options);
|
||||
// drush_op() provides --simulate support.
|
||||
drush_op([$executable, 'import']);
|
||||
if ($count = $executable->getFailedCount()) {
|
||||
// Nudge Drush to use a non-zero exit code.
|
||||
throw new \Exception(
|
||||
dt(
|
||||
'!name Migration - !count failed.',
|
||||
['!name' => $migration_id, '!count' => $count]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migrate message logger.
|
||||
*
|
||||
* @return \Drupal\migrate\MigrateMessageInterface
|
||||
* The migrate message service.
|
||||
*/
|
||||
protected function getMigrateMessage() {
|
||||
if (!isset($this->migrateMessage)) {
|
||||
$this->migrateMessage = new Drush9LogMigrateMessage($this->logger());
|
||||
}
|
||||
return $this->migrateMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Returns responses for migrate_tools message routes.
|
||||
*/
|
||||
class MessageController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The database service.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* Plugin manager for migration plugins.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('database'),
|
||||
$container->get('plugin.manager.migration')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessageController object.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* A database connection.
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
||||
* The migration plugin manager.
|
||||
*/
|
||||
public function __construct(Connection $database, MigrationPluginManagerInterface $migration_plugin_manager) {
|
||||
$this->database = $database;
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of log level classes.
|
||||
*
|
||||
* @return array
|
||||
* An array of log level classes.
|
||||
*/
|
||||
public static function getLogLevelClassMap() {
|
||||
return [
|
||||
MigrationInterface::MESSAGE_INFORMATIONAL => 'migrate-message-4',
|
||||
MigrationInterface::MESSAGE_NOTICE => 'migrate-message-3',
|
||||
MigrationInterface::MESSAGE_WARNING => 'migrate-message-2',
|
||||
MigrationInterface::MESSAGE_ERROR => 'migrate-message-1',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a listing of migration messages.
|
||||
*
|
||||
* Messages are truncated at 56 chars.
|
||||
*
|
||||
* @param string $migration_group
|
||||
* Machine name of the migration's group.
|
||||
*
|
||||
* @param string $migration
|
||||
* Machine name of the migration.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function overview($migration_group, $migration) {
|
||||
$rows = [];
|
||||
$classes = static::getLogLevelClassMap();
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration);
|
||||
$source_id_field_names = array_keys($migration->getSourcePlugin()->getIds());
|
||||
$column_number = 1;
|
||||
foreach ($source_id_field_names as $source_id_field_name) {
|
||||
$header[] = [
|
||||
'data' => $source_id_field_name,
|
||||
'field' => 'sourceid' . $column_number++,
|
||||
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
|
||||
];
|
||||
}
|
||||
$header[] = [
|
||||
'data' => $this->t('Severity level'),
|
||||
'field' => 'level',
|
||||
'class' => [RESPONSIVE_PRIORITY_LOW],
|
||||
];
|
||||
$header[] = [
|
||||
'data' => $this->t('Message'),
|
||||
'field' => 'message',
|
||||
];
|
||||
|
||||
$message_table = $migration->getIdMap()->messageTableName();
|
||||
$map_table = $migration->getIdMap()->mapTableName();
|
||||
$query = $this->database->select($message_table, 'msg')
|
||||
->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->extend('\Drupal\Core\Database\Query\TableSortExtender');
|
||||
$query->innerJoin($map_table, 'map', 'msg.source_ids_hash=map.source_ids_hash');
|
||||
$query->fields('msg');
|
||||
$query->fields('map');
|
||||
$result = $query
|
||||
->limit(50)
|
||||
->orderByHeader($header)
|
||||
->execute();
|
||||
|
||||
foreach ($result as $message_row) {
|
||||
$column_number = 1;
|
||||
foreach ($source_id_field_names as $source_id_field_name) {
|
||||
$column_name = 'sourceid' . $column_number++;
|
||||
$row[$column_name] = $message_row->$column_name;
|
||||
}
|
||||
$row['level'] = $message_row->level;
|
||||
$row['message'] = $message_row->message;
|
||||
$row['class'] = [Html::getClass('migrate-message-' . $message_row->level), $classes[$message_row->level]];
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$build['message_table'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#attributes' => ['id' => $message_table, 'class' => [$message_table]],
|
||||
'#empty' => $this->t('No messages for this migration.'),
|
||||
];
|
||||
$build['message_pager'] = ['#type' => 'pager'];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Routing\CurrentRouteMatch;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\migrate_tools\MigrateBatchExecutable;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\migrate\MigrateMessage;
|
||||
|
||||
/**
|
||||
* Returns responses for migrate_tools migration view routes.
|
||||
*/
|
||||
class MigrationController extends ControllerBase implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* Plugin manager for migration plugins.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\CurrentRouteMatch
|
||||
*/
|
||||
protected $currentRouteMatch;
|
||||
|
||||
/**
|
||||
* Constructs a new MigrationController object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
||||
* The plugin manager for config entity-based migrations.
|
||||
* @param \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatch
|
||||
* The current route match.
|
||||
*/
|
||||
public function __construct(MigrationPluginManagerInterface $migration_plugin_manager, CurrentRouteMatch $currentRouteMatch) {
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
$this->currentRouteMatch = $currentRouteMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.migration'),
|
||||
$container->get('current_route_match')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an overview of a migration entity.
|
||||
*
|
||||
* @param string $migration_group
|
||||
* Machine name of the migration's group.
|
||||
* @param string $migration
|
||||
* Machine name of the migration.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function overview($migration_group, $migration) {
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration);
|
||||
|
||||
$build['overview'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Overview'),
|
||||
];
|
||||
|
||||
$build['overview']['group'] = [
|
||||
'#title' => $this->t('Group:'),
|
||||
'#markup' => Xss::filterAdmin($migration_group),
|
||||
'#type' => 'item',
|
||||
];
|
||||
|
||||
$build['overview']['description'] = [
|
||||
'#title' => $this->t('Description:'),
|
||||
'#markup' => Xss::filterAdmin($migration->label()),
|
||||
'#type' => 'item',
|
||||
];
|
||||
|
||||
$migration_dependencies = $migration->getMigrationDependencies();
|
||||
if (!empty($migration_dependencies['required'])) {
|
||||
$build['overview']['dependencies'] = [
|
||||
'#title' => $this->t('Migration Dependencies') ,
|
||||
'#markup' => Xss::filterAdmin(implode(', ', $migration_dependencies['required'])),
|
||||
'#type' => 'item',
|
||||
];
|
||||
}
|
||||
if (!empty($migration_dependencies['optional'])) {
|
||||
$build['overview']['soft_dependencies'] = [
|
||||
'#title' => $this->t('Soft Migration Dependencies'),
|
||||
'#markup' => Xss::filterAdmin(implode(', ', $migration_dependencies['optional'])),
|
||||
'#type' => 'item',
|
||||
];
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display source information of a migration entity.
|
||||
*
|
||||
* @param string $migration_group
|
||||
* Machine name of the migration's group.
|
||||
* @param string $migration
|
||||
* Machine name of the migration.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function source($migration_group, $migration) {
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration);
|
||||
|
||||
// Source field information.
|
||||
$build['source'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Source'),
|
||||
'#group' => 'detail',
|
||||
'#description' => $this->t('<p>These are the fields available from the source of this migration task. The machine names listed here may be used as sources in the process pipeline.</p>'),
|
||||
'#attributes' => [
|
||||
'id' => 'migration-detail-source',
|
||||
],
|
||||
];
|
||||
|
||||
$source = $migration->getSourcePlugin();
|
||||
$build['source']['query'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Query'),
|
||||
'#markup' => '<pre>' . Xss::filterAdmin($source) . '</pre>',
|
||||
];
|
||||
$header = [$this->t('Machine name'), $this->t('Description')];
|
||||
$rows = [];
|
||||
foreach ($source->fields($migration) as $machine_name => $description) {
|
||||
$rows[] = [
|
||||
['data' => Html::escape($machine_name)],
|
||||
['data' => Xss::filterAdmin($description)],
|
||||
];
|
||||
}
|
||||
|
||||
$build['source']['fields'] = [
|
||||
'#theme' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('No fields'),
|
||||
];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a migration.
|
||||
*
|
||||
* @param string $migration_group
|
||||
* Machine name of the migration's group.
|
||||
* @param string $migration
|
||||
* Machine name of the migration.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function run($migration_group, $migration) {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration);
|
||||
|
||||
$migrateMessage = new MigrateMessage();
|
||||
$options = [];
|
||||
|
||||
$executable = new MigrateBatchExecutable($migration, $migrateMessage, $options);
|
||||
$executable->batchImport();
|
||||
|
||||
$migration_group = $this->currentRouteMatch->getParameter('migration_group');
|
||||
$route_parameters = [
|
||||
'migration_group' => $migration_group,
|
||||
'migration' => $migration->id(),
|
||||
];
|
||||
return batch_process(Url::fromRoute('entity.migration.process', $route_parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display process information of a migration entity.
|
||||
*
|
||||
* @param string $migration_group
|
||||
* Machine name of the migration's group.
|
||||
* @param string $migration
|
||||
* Machine name of the migration.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function process($migration_group, $migration) {
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration);
|
||||
|
||||
// Process information.
|
||||
$build['process'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Process'),
|
||||
];
|
||||
|
||||
$header = [
|
||||
$this->t('Destination'),
|
||||
$this->t('Source'),
|
||||
$this->t('Process plugin'),
|
||||
$this->t('Default'),
|
||||
];
|
||||
$rows = [];
|
||||
foreach ($migration->getProcess() as $destination_id => $process_line) {
|
||||
$row = [];
|
||||
$row[] = ['data' => Html::escape($destination_id)];
|
||||
if (isset($process_line[0]['source'])) {
|
||||
$row[] = ['data' => Xss::filterAdmin($process_line[0]['source'])];
|
||||
}
|
||||
else {
|
||||
$row[] = '';
|
||||
}
|
||||
if (isset($process_line[0]['plugin'])) {
|
||||
$row[] = ['data' => Xss::filterAdmin($process_line[0]['plugin'])];
|
||||
}
|
||||
else {
|
||||
$row[] = '';
|
||||
}
|
||||
if (isset($process_line[0]['default_value'])) {
|
||||
$row[] = ['data' => Xss::filterAdmin($process_line[0]['default_value'])];
|
||||
}
|
||||
else {
|
||||
$row[] = '';
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$build['process']['fields'] = [
|
||||
'#theme' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('No process defined.'),
|
||||
];
|
||||
|
||||
$build['process']['run'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->t('Run'),
|
||||
'#url' => Url::fromRoute('entity.migration.process.run', ['migration_group' => $migration_group, 'migration' => $migration->id()]),
|
||||
];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays destination information of a migration entity.
|
||||
*
|
||||
* @param string $migration_group
|
||||
* Machine name of the migration's group.
|
||||
* @param string $migration
|
||||
* Machine name of the migration.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function destination($migration_group, $migration) {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration);
|
||||
|
||||
// Destination field information.
|
||||
$build['destination'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $this->t('Destination'),
|
||||
'#group' => 'detail',
|
||||
'#description' => $this->t('<p>These are the fields available in the destination plugin of this migration task. The machine names are those available to be used as the keys in the process pipeline.</p>'),
|
||||
'#attributes' => [
|
||||
'id' => 'migration-detail-destination',
|
||||
],
|
||||
];
|
||||
$destination = $migration->getDestinationPlugin();
|
||||
$build['destination']['type'] = [
|
||||
'#type' => 'item',
|
||||
'#title' => $this->t('Type'),
|
||||
'#markup' => Xss::filterAdmin($destination->getPluginId()),
|
||||
];
|
||||
$header = [$this->t('Machine name'), $this->t('Description')];
|
||||
$rows = [];
|
||||
$destination_fields = $destination->fields() ?: [];
|
||||
foreach ($destination_fields as $machine_name => $description) {
|
||||
$rows[] = [
|
||||
['data' => Html::escape($machine_name)],
|
||||
['data' => Xss::filterAdmin($description)],
|
||||
];
|
||||
}
|
||||
|
||||
$build['destination']['fields'] = [
|
||||
'#theme' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('No fields'),
|
||||
];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Controller;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a listing of migration group entities.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Controller
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationGroupListBuilder extends ConfigEntityListBuilder {
|
||||
|
||||
/**
|
||||
* Builds the header row for the entity listing.
|
||||
*
|
||||
* @return array
|
||||
* A render array structure of header strings.
|
||||
*
|
||||
* @see Drupal\Core\Entity\EntityListController::render()
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Migration Group');
|
||||
$header['machine_name'] = $this->t('Machine Name');
|
||||
$header['description'] = $this->t('Description');
|
||||
$header['source_type'] = $this->t('Source Type');
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a row for an entity in the entity listing.
|
||||
*
|
||||
* @param EntityInterface $entity
|
||||
* The entity for which to build the row.
|
||||
*
|
||||
* @return array
|
||||
* A render array of the table row for displaying the entity.
|
||||
*
|
||||
* @see Drupal\Core\Entity\EntityListController::render()
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['label'] = $entity->label();
|
||||
$row['machine_name'] = $entity->id();
|
||||
$row['description'] = $entity->get('description');
|
||||
$row['source_type'] = $entity->get('source_type');
|
||||
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
$operations['list'] = [
|
||||
'title' => $this->t('List migrations'),
|
||||
'weight' => 0,
|
||||
'url' => Url::fromRoute('entity.migration.list', ['migration_group' => $entity->id()]),
|
||||
];
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Controller;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Routing\CurrentRouteMatch;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Drupal\migrate_plus\Entity\MigrationGroup;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a listing of migration entities in a given group.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Controller
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationListBuilder extends ConfigEntityListBuilder implements EntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* Default object for current_route_match service.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\CurrentRouteMatch
|
||||
*/
|
||||
protected $currentRouteMatch;
|
||||
|
||||
/**
|
||||
* Plugin manager for migration plugins.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The entity storage class.
|
||||
* @param \Drupal\Core\Routing\CurrentRouteMatch $current_route_match
|
||||
* The current route match service.
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
||||
* The plugin manager for config entity-based migrations.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, CurrentRouteMatch $current_route_match, MigrationPluginManagerInterface $migration_plugin_manager) {
|
||||
parent::__construct($entity_type, $storage);
|
||||
$this->currentRouteMatch = $current_route_match;
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity.manager')->getStorage($entity_type->id()),
|
||||
$container->get('current_route_match'),
|
||||
$container->get('plugin.manager.migration')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the migrations belonging to the appropriate group.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity IDs.
|
||||
*/
|
||||
protected function getEntityIds() {
|
||||
$migration_group = $this->currentRouteMatch->getParameter('migration_group');
|
||||
|
||||
$query = $this->getStorage()->getQuery()
|
||||
->sort($this->entityType->getKey('id'));
|
||||
|
||||
$migration_groups = MigrationGroup::loadMultiple();
|
||||
|
||||
if (array_key_exists($migration_group, $migration_groups)) {
|
||||
$query->condition('migration_group', $migration_group);
|
||||
}
|
||||
else {
|
||||
$query->notExists('migration_group');
|
||||
}
|
||||
// Only add the pager if a limit is specified.
|
||||
if ($this->limit) {
|
||||
$query->pager($this->limit);
|
||||
}
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the header row for the entity listing.
|
||||
*
|
||||
* @return array
|
||||
* A render array structure of header strings.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityListController::render()
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Migration');
|
||||
$header['machine_name'] = $this->t('Machine Name');
|
||||
$header['status'] = $this->t('Status');
|
||||
$header['total'] = $this->t('Total');
|
||||
$header['imported'] = $this->t('Imported');
|
||||
$header['unprocessed'] = $this->t('Unprocessed');
|
||||
$header['messages'] = $this->t('Messages');
|
||||
$header['last_imported'] = $this->t('Last Imported');
|
||||
$header['operations'] = $this->t('Operations');
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a row for a migration plugin.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $migration_entity
|
||||
* The migration plugin for which to build the row.
|
||||
*
|
||||
* @return array
|
||||
* A render array of the table row for displaying the plugin information.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityListController::render()
|
||||
*/
|
||||
public function buildRow(EntityInterface $migration_entity) {
|
||||
try {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration_entity->id());
|
||||
$migration_group = $migration->get('migration_group');
|
||||
if (!$migration_group) {
|
||||
$migration_group = 'default';
|
||||
}
|
||||
$route_parameters = [
|
||||
'migration_group' => $migration_group,
|
||||
'migration' => $migration->id(),
|
||||
];
|
||||
$row['label'] = [
|
||||
'data' => [
|
||||
'#type' => 'link',
|
||||
'#title' => $migration->label(),
|
||||
'#url' => Url::fromRoute("entity.migration.overview", $route_parameters),
|
||||
],
|
||||
];
|
||||
$row['machine_name'] = $migration->id();
|
||||
$row['status'] = $migration->getStatusLabel();
|
||||
} catch (PluginException $e) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
// Derive the stats.
|
||||
$source_plugin = $migration->getSourcePlugin();
|
||||
$row['total'] = $source_plugin->count();
|
||||
$map = $migration->getIdMap();
|
||||
$row['imported'] = $map->importedCount();
|
||||
// -1 indicates uncountable sources.
|
||||
if ($row['total'] == -1) {
|
||||
$row['total'] = $this->t('N/A');
|
||||
$row['unprocessed'] = $this->t('N/A');
|
||||
}
|
||||
else {
|
||||
$row['unprocessed'] = $row['total'] - $map->processedCount();
|
||||
}
|
||||
$row['messages'] = [
|
||||
'data' => [
|
||||
'#type' => 'link',
|
||||
'#title' => $map->messageCount(),
|
||||
'#url' => Url::fromRoute("migrate_tools.messages", $route_parameters),
|
||||
],
|
||||
];
|
||||
$migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported');
|
||||
$last_imported = $migrate_last_imported_store->get($migration->id(), FALSE);
|
||||
if ($last_imported) {
|
||||
/** @var \Drupal\Core\Datetime\DateFormatter $date_formatter */
|
||||
$date_formatter = \Drupal::service('date.formatter');
|
||||
$row['last_imported'] = $date_formatter->format($last_imported / 1000,
|
||||
'custom', 'Y-m-d H:i:s');
|
||||
}
|
||||
else {
|
||||
$row['last_imported'] = '';
|
||||
}
|
||||
|
||||
$row['operations']['data'] = [
|
||||
'#type' => 'dropbutton',
|
||||
'#links' => [
|
||||
'simple_form' => [
|
||||
'title' => $this->t('Execute'),
|
||||
'url' => Url::fromRoute('migrate_tools.execute', [
|
||||
'migration_group' => $migration_group,
|
||||
'migration' => $migration->id(),
|
||||
]),
|
||||
],
|
||||
],
|
||||
];
|
||||
} catch (PluginException $e) {
|
||||
// Derive the stats.
|
||||
$row['status'] = $this->t('No data found');
|
||||
$row['total'] = $this->t('N/A');
|
||||
$row['imported'] = $this->t('N/A');
|
||||
$row['unprocessed'] = $this->t('N/A');
|
||||
$row['messages'] = $this->t('N/A');
|
||||
$row['last_imported'] = $this->t('N/A');
|
||||
$row['operations'] = $this->t('N/A');
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The URL associated with an operation.
|
||||
*
|
||||
* @param $migration_group
|
||||
* The migration's parent group.
|
||||
*/
|
||||
protected function addGroupParameter(Url $url, $migration_group) {
|
||||
if (!$migration_group) {
|
||||
$migration_group = 'default';
|
||||
}
|
||||
$route_parameters = $url->getRouteParameters() + ['migration_group' => $migration_group];
|
||||
$url->setRouteParameters($route_parameters);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools;
|
||||
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Print message in drush from migrate message. Drush 9 version.
|
||||
*
|
||||
* @package Drupal\migrate_tools
|
||||
*/
|
||||
class Drush9LogMigrateMessage implements MigrateMessageInterface, LoggerAwareInterface {
|
||||
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* The map between migrate status and drush log levels.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $map = [
|
||||
'status' => 'notice',
|
||||
];
|
||||
|
||||
/**
|
||||
* DrushLogMigrateMessage constructor.
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger) {
|
||||
$this->setLogger($logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a message from the migration.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to display.
|
||||
* @param string $type
|
||||
* The type of message to display.
|
||||
*/
|
||||
public function display($message, $type = 'status') {
|
||||
$type = isset($this->map[$type]) ? $this->map[$type] : $type;
|
||||
$this->logger->log($type, $message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools;
|
||||
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
|
||||
class DrushLogMigrateMessage implements MigrateMessageInterface {
|
||||
|
||||
/**
|
||||
* Output a message from the migration.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to display.
|
||||
* @param string $type
|
||||
* The type of message to display.
|
||||
*
|
||||
* @see drush_log()
|
||||
*/
|
||||
public function display($message, $type = 'status') {
|
||||
drush_log($message, $type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class MigrationAddForm.
|
||||
*
|
||||
* Provides the add form for our migration entity.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationAddForm extends MigrationFormBase {
|
||||
|
||||
/**
|
||||
* Returns the actions provided by this form.
|
||||
*
|
||||
* For our add form, we only need to change the text of the submit button.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
unset($actions['submit']);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class MigrationDeleteForm.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationDeleteForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* Gathers a confirmation question.
|
||||
*
|
||||
* @return string
|
||||
* Translated string.
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete migration %label?', [
|
||||
'%label' => $this->entity->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather the confirmation text.
|
||||
*
|
||||
* @return string
|
||||
* Translated string.
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete Migration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cancel URL.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The URL to go to if the user cancels the deletion.
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.migration_group.list');
|
||||
}
|
||||
|
||||
/**
|
||||
* The submit handler for the confirm form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Delete the entity.
|
||||
$this->entity->delete();
|
||||
|
||||
// Set a message that the entity was deleted.
|
||||
drupal_set_message(t('Migration %label was deleted.', [
|
||||
'%label' => $this->entity->label(),
|
||||
]));
|
||||
|
||||
// Redirect the user to the list controller when complete.
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Class MigrationEditForm
|
||||
*
|
||||
* Provides the edit form for our Migration entity.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationEditForm extends MigrationFormBase {
|
||||
|
||||
/**
|
||||
* Returns the actions provided by this form.
|
||||
*
|
||||
* For the edit form, we only need to change the text of the submit button.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
public function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = t('Update Migration');
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The URL associated with an operation.
|
||||
*
|
||||
* @param $migration_group
|
||||
* The migration's parent group.
|
||||
*/
|
||||
protected function addGroupParameter(Url $url, $migration_group) {
|
||||
$route_parameters = $url->getRouteParameters() + ['migration_group' => $migration_group];
|
||||
$url->setRouteParameters($route_parameters);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate_tools\Form\MigrationExecuteForm
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\MigrateMessage;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Plugin\Migration;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Drupal\migrate_tools\MigrateBatchExecutable;
|
||||
use Drupal\migrate_tools\MigrateExecutable;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This form is specifically for configuring process pipelines.
|
||||
*/
|
||||
class MigrationExecuteForm extends FormBase {
|
||||
|
||||
/**
|
||||
* Plugin manager for migration plugins.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* Constructs a new MigrationExecuteForm object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
||||
* The plugin manager for config entity-based migrations.
|
||||
*/
|
||||
public function __construct(MigrationPluginManagerInterface $migration_plugin_manager) {
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.migration')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'migration_execute_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
|
||||
$form = [];
|
||||
|
||||
$form['operations'] = $this->migrateMigrateOperations();
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Operations.
|
||||
*/
|
||||
private function migrateMigrateOperations() {
|
||||
// Build the 'Update options' form.
|
||||
$form = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Operations'),
|
||||
];
|
||||
$options = [
|
||||
'import' => t('Import'),
|
||||
'rollback' => t('Rollback'),
|
||||
'stop' => t('Stop'),
|
||||
'reset' => t('Reset'),
|
||||
];
|
||||
$form['operation'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('Choose an operation to run'),
|
||||
'#options' => $options,
|
||||
'#default_value' => 'import',
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Execute'),
|
||||
];
|
||||
$definitions = [];
|
||||
$definitions[] = $this->t('Import: Imports all previously unprocessed records from the source, plus any records marked for update, into destination Drupal objects.');
|
||||
$definitions[] = $this->t('Rollback: Deletes all Drupal objects created by the import.');
|
||||
$definitions[] = $this->t('Stop: Cleanly interrupts any import or rollback processes that may currently be running.');
|
||||
$definitions[] = $this->t('Reset: Sometimes a process may fail to stop cleanly, and be left stuck in an Importing or Rolling Back status. Choose Reset to clear the status and permit other operations to proceed.');
|
||||
$form['definitions'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#title' => $this->t('Definitions'),
|
||||
'#list_type' => 'ul',
|
||||
'#items' => $definitions,
|
||||
];
|
||||
|
||||
$form['options'] = [
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Options'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
];
|
||||
$form['options']['update'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Update'),
|
||||
'#description' => t('Check this box to update all previously-imported content
|
||||
in addition to importing new content. Leave unchecked to only import
|
||||
new content'),
|
||||
];
|
||||
$form['options']['force'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Ignore dependencies'),
|
||||
'#description' => t('Check this box to ignore dependencies when running imports
|
||||
- all tasks will run whether or not their dependent tasks have
|
||||
completed.'),
|
||||
];
|
||||
// @TODO: Limit is not working. Perhaps because of batch? See
|
||||
// https://www.drupal.org/project/migrate_tools/issues/2924298
|
||||
// $form['options']['limit'] = [
|
||||
// '#type' => 'textfield',
|
||||
// '#title' => t('Limit to:'),
|
||||
// '#size' => 10,
|
||||
// '#description' => t('Set a limit of how many items to process for each migration task.'),
|
||||
// ];
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
if (empty($form_state->getValue('operation'))) {
|
||||
$form_state->setErrorByName('operation', $this->t('Please select an operation.'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
|
||||
$operation = $form_state->getValue('operation');
|
||||
|
||||
if ($form_state->getValue('limit')) {
|
||||
$limit = $form_state->getValue('limit');
|
||||
}
|
||||
else {
|
||||
$limit = 0;
|
||||
}
|
||||
|
||||
if ($form_state->getValue('update')) {
|
||||
$update = $form_state->getValue('update');
|
||||
}
|
||||
else {
|
||||
$update = 0;
|
||||
}
|
||||
if ($form_state->getValue('force')) {
|
||||
$force = $form_state->getValue('force');
|
||||
}
|
||||
else {
|
||||
$force = 0;
|
||||
}
|
||||
|
||||
$migration_name = \Drupal::routeMatch()->getParameter('migration');
|
||||
|
||||
if ($migration_name) {
|
||||
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration_name);
|
||||
$migrateMessage = new MigrateMessage();
|
||||
|
||||
switch ($operation) {
|
||||
case 'import':
|
||||
|
||||
$options = [
|
||||
'limit' => $limit,
|
||||
'update' => $update,
|
||||
'force' => $force,
|
||||
];
|
||||
|
||||
$executable = new MigrateBatchExecutable($migration, $migrateMessage, $options);
|
||||
$executable->batchImport();
|
||||
|
||||
break;
|
||||
|
||||
case 'rollback':
|
||||
|
||||
$options = [
|
||||
'limit' => $limit,
|
||||
'update' => $update,
|
||||
'force' => $force
|
||||
];
|
||||
|
||||
$executable = new MigrateBatchExecutable($migration, $migrateMessage, $options);
|
||||
$executable->rollback();
|
||||
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
|
||||
$migration->interruptMigration(MigrationInterface::RESULT_STOPPED);
|
||||
|
||||
break;
|
||||
|
||||
case 'reset':
|
||||
|
||||
$migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\migrate_plus\Entity\MigrationGroup;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class MigrationFormBase.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationFormBase extends EntityForm {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $entityQueryFactory;
|
||||
|
||||
/**
|
||||
* Construct the MigrationGroupFormBase.
|
||||
*
|
||||
* For simple entity forms, there's no need for a constructor. Our migration form
|
||||
* base, however, requires an entity query factory to be injected into it
|
||||
* from the container. We later use this query factory to build an entity
|
||||
* query for the exists() method.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* An entity query factory for the migration group entity type.
|
||||
*/
|
||||
public function __construct(QueryFactory $query_factory) {
|
||||
$this->entityQueryFactory = $query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for MigrationFormBase.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* A container interface service.
|
||||
*
|
||||
* @return \Drupal\migrate_tools\Form\MigrationGroupFormBase
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity.query'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::form().
|
||||
*
|
||||
* Builds the entity add/edit form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the migration add/edit form.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Get anything we need from the base class.
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->entity;
|
||||
|
||||
$form['warning'] = [
|
||||
'#markup' => $this->t('Creating migrations is not yet supported. See <a href=":url">:url</a>', [
|
||||
':url' => 'https://www.drupal.org/node/2573241',
|
||||
])
|
||||
];
|
||||
|
||||
// Build the form.
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $migration->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#title' => $this->t('Machine name'),
|
||||
'#default_value' => $migration->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
|
||||
'error' => 'The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".',
|
||||
],
|
||||
'#disabled' => !$migration->isNew(),
|
||||
];
|
||||
|
||||
$groups = MigrationGroup::loadMultiple();
|
||||
$group_options = [];
|
||||
foreach ($groups as $group) {
|
||||
$group_options[$group->id()] = $group->label();
|
||||
}
|
||||
if (!$migration->get('migration_group') && isset($group_options['default'])) {
|
||||
$migration->set('migration_group', 'default');
|
||||
}
|
||||
|
||||
$form['migration_group'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Migration Group'),
|
||||
'#empty_value' => '',
|
||||
'#default_value' => $migration->get('migration_group'),
|
||||
'#options' => $group_options,
|
||||
'#description' => $this->t('Assign this migration to an existing group.'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for an existing migration group.
|
||||
*
|
||||
* @param string|int $entity_id
|
||||
* The entity ID.
|
||||
* @param array $element
|
||||
* The form element.
|
||||
* @param FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this format already exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($entity_id, array $element, FormStateInterface $form_state) {
|
||||
// Use the query factory to build a new migration entity query.
|
||||
$query = $this->entityQueryFactory->get('migration');
|
||||
|
||||
// Query the entity ID to see if its in use.
|
||||
$result = $query->condition('id', $element['#field_prefix'] . $entity_id)
|
||||
->execute();
|
||||
|
||||
// We don't need to return the ID, only if it exists or not.
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::actions().
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
// Get the basic actins from the base class.
|
||||
$actions = parent::actions($form, $form_state);
|
||||
|
||||
// Change the submit button text.
|
||||
$actions['submit']['#value'] = $this->t('Save');
|
||||
|
||||
// Return the result.
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::save().
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$migration = $this->getEntity();
|
||||
$status = $migration->save();
|
||||
|
||||
if ($status == SAVED_UPDATED) {
|
||||
// If we edited an existing entity...
|
||||
drupal_set_message($this->t('Migration %label has been updated.', ['%label' => $migration->label()]));
|
||||
}
|
||||
else {
|
||||
// If we created a new entity...
|
||||
drupal_set_message($this->t('Migration %label has been added.', ['%label' => $migration->label()]));
|
||||
}
|
||||
|
||||
// Redirect the user back to the listing route after the save operation.
|
||||
$form_state->setRedirect('entity.migration.list',
|
||||
['migration_group' => $migration->get('migration_group')]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class MigrationGroupAddForm.
|
||||
*
|
||||
* Provides the add form for our migration_group entity.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationGroupAddForm extends MigrationGroupFormBase {
|
||||
|
||||
/**
|
||||
* Returns the actions provided by this form.
|
||||
*
|
||||
* For our add form, we only need to change the text of the submit button.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = $this->t('Create Migration Group');
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class MigrationGroupDeleteForm.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationGroupDeleteForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* Gathers a confirmation question.
|
||||
*
|
||||
* @return string
|
||||
* Translated string.
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete migration group %label?', [
|
||||
'%label' => $this->entity->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather the confirmation text.
|
||||
*
|
||||
* @return string
|
||||
* Translated string.
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete Migration Group');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cancel URL.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The URL to go to if the user cancels the deletion.
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.migration_group.list');
|
||||
}
|
||||
|
||||
/**
|
||||
* The submit handler for the confirm form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Delete the entity.
|
||||
$this->entity->delete();
|
||||
|
||||
// Set a message that the entity was deleted.
|
||||
drupal_set_message(t('Migration group %label was deleted.', [
|
||||
'%label' => $this->entity->label(),
|
||||
]));
|
||||
|
||||
// Redirect the user to the list controller when complete.
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Class MigrationGroupEditForm
|
||||
*
|
||||
* Provides the edit form for our Migration Group entity.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationGroupEditForm extends MigrationGroupFormBase {
|
||||
|
||||
/**
|
||||
* Returns the actions provided by this form.
|
||||
*
|
||||
* For the edit form, we only need to change the text of the submit button.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
public function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
$actions['submit']['#value'] = t('Update Migration Group');
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class MigrationGroupFormBase.
|
||||
*
|
||||
* @package Drupal\migrate_tools\Form
|
||||
*
|
||||
* @ingroup migrate_tools
|
||||
*/
|
||||
class MigrationGroupFormBase extends EntityForm {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $entityQueryFactory;
|
||||
|
||||
/**
|
||||
* Construct the MigrationGroupFormBase.
|
||||
*
|
||||
* For simple entity forms, there's no need for a constructor. Our migration group form
|
||||
* base, however, requires an entity query factory to be injected into it
|
||||
* from the container. We later use this query factory to build an entity
|
||||
* query for the exists() method.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
|
||||
* An entity query factory for the migration group entity type.
|
||||
*/
|
||||
public function __construct(QueryFactory $query_factory) {
|
||||
$this->entityQueryFactory = $query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for MigrationGroupFormBase.
|
||||
*
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* A container interface service.
|
||||
*
|
||||
* @return \Drupal\migrate_tools\Form\MigrationFormBase
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity.query'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::form().
|
||||
*
|
||||
* Builds the entity add/edit form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the migration group add/edit form.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
// Get anything we need from the base class.
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
/** @var \Drupal\migrate_plus\Entity\MigrationGroupInterface $migration_group */
|
||||
$migration_group = $this->entity;
|
||||
|
||||
// Build the form.
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $migration_group->label(),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#title' => $this->t('Machine name'),
|
||||
'#default_value' => $migration_group->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
|
||||
'error' => 'The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".',
|
||||
],
|
||||
'#disabled' => !$migration_group->isNew(),
|
||||
];
|
||||
$form['description'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Description'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $migration_group->get('description'),
|
||||
];
|
||||
$form['source_type'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Source type'),
|
||||
'#description' => $this->t('Type of source system the group is migrating from, for example "Drupal 6" or "WordPress 4".'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $migration_group->get('source_type'),
|
||||
];
|
||||
|
||||
// Return the form.
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for an existing migration group.
|
||||
*
|
||||
* @param string|int $entity_id
|
||||
* The entity ID.
|
||||
* @param array $element
|
||||
* The form element.
|
||||
* @param FormStateInterface $form_state
|
||||
* The form state.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this format already exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($entity_id, array $element, FormStateInterface $form_state) {
|
||||
// Use the query factory to build a new migration group entity query.
|
||||
$query = $this->entityQueryFactory->get('migration_group');
|
||||
|
||||
// Query the entity ID to see if its in use.
|
||||
$result = $query->condition('id', $element['#field_prefix'] . $entity_id)
|
||||
->execute();
|
||||
|
||||
// We don't need to return the ID, only if it exists or not.
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::actions().
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return array
|
||||
* An array of supported actions for the current entity form.
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
// Get the basic actins from the base class.
|
||||
$actions = parent::actions($form, $form_state);
|
||||
|
||||
// Change the submit button text.
|
||||
$actions['submit']['#value'] = $this->t('Save');
|
||||
|
||||
// Return the result.
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\Entity\EntityFormController::save().
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* An associative array containing the current state of the form.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$migration_group = $this->getEntity();
|
||||
$status = $migration_group->save();
|
||||
|
||||
if ($status == SAVED_UPDATED) {
|
||||
// If we edited an existing entity...
|
||||
drupal_set_message($this->t('Migration group %label has been updated.', ['%label' => $migration_group->label()]));
|
||||
}
|
||||
else {
|
||||
// If we created a new entity...
|
||||
drupal_set_message($this->t('Migration group %label has been added.', ['%label' => $migration_group->label()]));
|
||||
}
|
||||
|
||||
// Redirect the user back to the listing route after the save operation.
|
||||
$form_state->setRedirect('entity.migration_group.list');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools;
|
||||
|
||||
use Drupal\migrate\MigrateMessage;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Plugin\Migration;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Defines a migrate executable class for batch migrations through UI.
|
||||
*/
|
||||
class MigrateBatchExecutable extends MigrateExecutable {
|
||||
|
||||
/**
|
||||
* Representing a batch import operation.
|
||||
*/
|
||||
const BATCH_IMPORT = 1;
|
||||
|
||||
/**
|
||||
* Indicates if we need to update existing rows or skip them.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $updateExistingRows = 0;
|
||||
|
||||
/**
|
||||
* Indicates if we need import dependent migrations also.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $checkDependencies = 0;
|
||||
|
||||
/**
|
||||
* The current batch context.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $batchContext = [];
|
||||
|
||||
/**
|
||||
* Plugin manager for migration plugins.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, array $options = []) {
|
||||
|
||||
if (isset($options['update'])) {
|
||||
$this->updateExistingRows = $options['update'];
|
||||
}
|
||||
|
||||
if (isset($options['force'])) {
|
||||
$this->checkDependencies = $options['force'];
|
||||
}
|
||||
|
||||
parent::__construct($migration, $message, $options);
|
||||
$this->migrationPluginManager = \Drupal::getContainer()->get('plugin.manager.migration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current batch content so listeners can update the messages.
|
||||
*
|
||||
* @param array $context
|
||||
*/
|
||||
public function setBatchContext(&$context) {
|
||||
$this->batchContext = &$context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the current batch context.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function &getBatchContext() {
|
||||
return $this->batchContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup batch operations for running the migration.
|
||||
*/
|
||||
public function batchImport() {
|
||||
// Create the batch operations for each migration that needs to be executed.
|
||||
// This includes the migration for this executable, but also the dependent
|
||||
// migrations.
|
||||
$operations = $this->batchOperations([$this->migration], 'import', [
|
||||
'limit' => $this->itemLimit,
|
||||
'update' => $this->updateExistingRows,
|
||||
'force' => $this->checkDependencies
|
||||
]);
|
||||
|
||||
if (count($operations) > 0) {
|
||||
$batch = [
|
||||
'operations' => $operations,
|
||||
'title' => t('Migrating %migrate', ['%migrate' => $this->migration->label()]),
|
||||
'init_message' => t('Start migrating %migrate', ['%migrate' => $this->migration->label()]),
|
||||
'progress_message' => t('Migrating %migrate', ['%migrate' => $this->migration->label()]),
|
||||
'error_message' => t('An error occurred while migrating %migrate.', ['%migrate' => $this->migration->label()]),
|
||||
'finished' => '\Drupal\migrate_tools\MigrateBatchExecutable::batchFinishedImport',
|
||||
];
|
||||
|
||||
batch_set($batch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate the batch operations for importing migrations.
|
||||
*
|
||||
* @param array $migrations
|
||||
* @param array $operation
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function batchOperations($migrations, $operation, $options = []) {
|
||||
|
||||
$operations = [];
|
||||
|
||||
/**
|
||||
* @var string $id
|
||||
* @var Migration $migration
|
||||
*/
|
||||
foreach ($migrations as $id => $migration) {
|
||||
|
||||
if (!empty($options['update'])) {
|
||||
$migration->getIdMap()->prepareUpdate();
|
||||
}
|
||||
|
||||
if (!empty($options['force'])) {
|
||||
$migration->set('requirements', []);
|
||||
}
|
||||
else {
|
||||
$dependencies = $migration->getMigrationDependencies();
|
||||
if (!empty($dependencies['required'])) {
|
||||
$required_migrations = $this->migrationPluginManager->createInstances($dependencies['required']);
|
||||
// For dependent migrations will need to be migrate all items.
|
||||
$dependent_options = $options;
|
||||
$dependent_options['limit'] = 0;
|
||||
$operations += $this->batchOperations($required_migrations, $operation, [
|
||||
'limit' => 0,
|
||||
'update' => $options['update'],
|
||||
'force' => $options['force']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$operations[] = [
|
||||
'\Drupal\migrate_tools\MigrateBatchExecutable::batchProcessImport',
|
||||
[$migration->id(), $options]
|
||||
];
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch 'operation' callback
|
||||
*
|
||||
* @param string $migration_id
|
||||
* @param array $options
|
||||
* @param array $context
|
||||
*
|
||||
*/
|
||||
static public function batchProcessImport($migration_id, $options, &$context) {
|
||||
|
||||
if (empty($context['sandbox'])) {
|
||||
$context['finished'] = 0;
|
||||
$context['sandbox'] = [];
|
||||
$context['sandbox']['total'] = 0;
|
||||
$context['sandbox']['counter'] = 0;
|
||||
$context['sandbox']['batch_limit'] = 0;
|
||||
$context['sandbox']['operation'] = MigrateBatchExecutable::BATCH_IMPORT;
|
||||
}
|
||||
|
||||
// Prepare the migration executable.
|
||||
$message = new MigrateMessage();
|
||||
/** @var MigrationInterface $migration */
|
||||
$migration = \Drupal::getContainer()->get('plugin.manager.migration')->createInstance($migration_id);
|
||||
$executable = new MigrateBatchExecutable($migration, $message, $options);
|
||||
|
||||
if (empty($context['sandbox']['total'])) {
|
||||
$context['sandbox']['total'] = $executable->getSource()->count();
|
||||
$context['sandbox']['batch_limit'] = $executable->calculateBatchLimit($context);
|
||||
$context['results'][$migration->id()] = [
|
||||
'@numitems' => 0,
|
||||
'@created' => 0,
|
||||
'@updated' => 0,
|
||||
'@failures' => 0,
|
||||
'@ignored' => 0,
|
||||
'@name' => $migration->id()
|
||||
];
|
||||
}
|
||||
|
||||
// Every iteration, we reset out batch counter.
|
||||
$context['sandbox']['batch_counter'] = 0;
|
||||
|
||||
// Make sure we know our batch context.
|
||||
$executable->setBatchContext($context);
|
||||
|
||||
// Do the import.
|
||||
$result = $executable->import();
|
||||
|
||||
// Store the result, we will need to combine the results of all our iterations.
|
||||
$context['results'][$migration->id()] = [
|
||||
'@numitems' => $context['results'][$migration->id()]['@numitems'] + $executable->getProcessedCount(),
|
||||
'@created' => $context['results'][$migration->id()]['@created'] + $executable->getCreatedCount(),
|
||||
'@updated' => $context['results'][$migration->id()]['@updated'] + $executable->getUpdatedCount(),
|
||||
'@failures' => $context['results'][$migration->id()]['@failures'] + $executable->getFailedCount(),
|
||||
'@ignored' => $context['results'][$migration->id()]['@ignored'] + $executable->getIgnoredCount(),
|
||||
'@name' => $migration->id()
|
||||
];
|
||||
|
||||
// Do some housekeeping.
|
||||
if (
|
||||
$result != MigrationInterface::RESULT_INCOMPLETE
|
||||
) {
|
||||
$context['finished'] = 1;
|
||||
}
|
||||
else {
|
||||
$context['sandbox']['counter'] = $context['results'][$migration->id()]['@numitems'];
|
||||
if ($context['sandbox']['counter'] <= $context['sandbox']['total']) {
|
||||
$context['finished'] = ((float) $context['sandbox']['counter'] / (float) $context['sandbox']['total']);
|
||||
$context['message'] = t('Importing %migration (@percent%).', [
|
||||
'%migration' => $migration->label(),
|
||||
'@percent' => (int) ($context['finished'] * 100)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finished callback for import batches.
|
||||
*
|
||||
* @param $success
|
||||
* @param $results
|
||||
* @param $operations
|
||||
* @param $elapsed
|
||||
*/
|
||||
static public function batchFinishedImport($success, $results, $operations, $elapsed) {
|
||||
if ($success) {
|
||||
foreach ($results as $migration_id => $result) {
|
||||
$singular_message = "Processed 1 item (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
|
||||
$plural_message = "Processed @numitems items (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
|
||||
drupal_set_message(\Drupal::translation()->formatPlural($result['@numitems'],
|
||||
$singular_message,
|
||||
$plural_message,
|
||||
$result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function checkStatus() {
|
||||
$status = parent::checkStatus();
|
||||
|
||||
if ($status == MigrationInterface::RESULT_COMPLETED) {
|
||||
// Do some batch housekeeping.
|
||||
$context = $this->getBatchContext();
|
||||
|
||||
if (!empty($context['sandbox']) && $context['sandbox']['operation'] == MigrateBatchExecutable::BATCH_IMPORT) {
|
||||
$context['sandbox']['batch_counter']++;
|
||||
if ($context['sandbox']['batch_counter'] >= $context['sandbox']['batch_limit']) {
|
||||
$status = MigrationInterface::RESULT_INCOMPLETE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates how much a single batch iteration will handle.
|
||||
*
|
||||
* @param $context
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function calculateBatchLimit($context) {
|
||||
// TODO Maybe we need some other more sophisticated logic here?
|
||||
return ceil($context['sandbox']['total'] / 100);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate_tools;
|
||||
|
||||
use Drupal\migrate\Event\MigratePreRowSaveEvent;
|
||||
use Drupal\migrate\Event\MigrateRollbackEvent;
|
||||
use Drupal\migrate\Event\MigrateRowDeleteEvent;
|
||||
use Drupal\migrate\MigrateExecutable as MigrateExecutableBase;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Event\MigrateEvents;
|
||||
use Drupal\migrate_plus\Event\MigrateEvents as MigratePlusEvents;
|
||||
use Drupal\migrate\Event\MigrateMapSaveEvent;
|
||||
use Drupal\migrate\Event\MigrateMapDeleteEvent;
|
||||
use Drupal\migrate\Event\MigrateImportEvent;
|
||||
use Drupal\migrate_plus\Event\MigratePrepareRowEvent;
|
||||
|
||||
class MigrateExecutable extends MigrateExecutableBase {
|
||||
|
||||
/**
|
||||
* Counters of map statuses.
|
||||
*
|
||||
* @var array
|
||||
* Set of counters, keyed by MigrateIdMapInterface::STATUS_* constant.
|
||||
*/
|
||||
protected $saveCounters = [
|
||||
MigrateIdMapInterface::STATUS_FAILED => 0,
|
||||
MigrateIdMapInterface::STATUS_IGNORED => 0,
|
||||
MigrateIdMapInterface::STATUS_IMPORTED => 0,
|
||||
MigrateIdMapInterface::STATUS_NEEDS_UPDATE => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* Counter of map deletions.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $deleteCounter = 0;
|
||||
|
||||
/**
|
||||
* Maximum number of items to process in this migration. 0 indicates no limit
|
||||
* is to be applied.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $itemLimit = 0;
|
||||
|
||||
/**
|
||||
* Frequency (in items) at which progress messages should be emitted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $feedback = 0;
|
||||
|
||||
/**
|
||||
* List of specific source IDs to import.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $idlist = [];
|
||||
|
||||
/**
|
||||
* Count of number of items processed so far in this migration.
|
||||
* @var int
|
||||
*/
|
||||
protected $counter = 0;
|
||||
|
||||
/**
|
||||
* Whether the destination item exists before saving.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $preExistingItem = FALSE;
|
||||
|
||||
/**
|
||||
* List of event listeners we have registered.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listeners = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, array $options = []) {
|
||||
parent::__construct($migration, $message);
|
||||
if (isset($options['limit'])) {
|
||||
$this->itemLimit = $options['limit'];
|
||||
}
|
||||
if (isset($options['feedback'])) {
|
||||
$this->feedback = $options['feedback'];
|
||||
}
|
||||
if (isset($options['idlist'])) {
|
||||
if (is_string($options['idlist'])) {
|
||||
$this->idlist = explode(',', $options['idlist']);
|
||||
array_walk($this->idlist, function (&$value, $key) {
|
||||
$value = explode(':', $value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$this->listeners[MigrateEvents::MAP_SAVE] = [$this, 'onMapSave'];
|
||||
$this->listeners[MigrateEvents::MAP_DELETE] = [$this, 'onMapDelete'];
|
||||
$this->listeners[MigrateEvents::POST_IMPORT] = [$this, 'onPostImport'];
|
||||
$this->listeners[MigrateEvents::POST_ROLLBACK] = [$this, 'onPostRollback'];
|
||||
$this->listeners[MigrateEvents::PRE_ROW_SAVE] = [$this, 'onPreRowSave'];
|
||||
$this->listeners[MigrateEvents::POST_ROW_DELETE] = [$this, 'onPostRowDelete'];
|
||||
$this->listeners[MigratePlusEvents::PREPARE_ROW] = [$this, 'onPrepareRow'];
|
||||
foreach ($this->listeners as $event => $listener) {
|
||||
\Drupal::service('event_dispatcher')->addListener($event, $listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count up any map save events.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateMapSaveEvent $event
|
||||
* The map event.
|
||||
*/
|
||||
public function onMapSave(MigrateMapSaveEvent $event) {
|
||||
// Only count saves for this migration.
|
||||
if ($event->getMap()->getQualifiedMapTableName() == $this->migration->getIdMap()->getQualifiedMapTableName()) {
|
||||
$fields = $event->getFields();
|
||||
// Distinguish between creation and update.
|
||||
if ($fields['source_row_status'] == MigrateIdMapInterface::STATUS_IMPORTED &&
|
||||
$this->preExistingItem
|
||||
) {
|
||||
$this->saveCounters[MigrateIdMapInterface::STATUS_NEEDS_UPDATE]++;
|
||||
}
|
||||
else {
|
||||
$this->saveCounters[$fields['source_row_status']]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count up any rollback events.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateMapDeleteEvent $event
|
||||
* The map event.
|
||||
*/
|
||||
public function onMapDelete(MigrateMapDeleteEvent $event) {
|
||||
$this->deleteCounter++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of items created.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCreatedCount() {
|
||||
return $this->saveCounters[MigrateIdMapInterface::STATUS_IMPORTED];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of items updated.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUpdatedCount() {
|
||||
return $this->saveCounters[MigrateIdMapInterface::STATUS_NEEDS_UPDATE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of items ignored.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIgnoredCount() {
|
||||
return $this->saveCounters[MigrateIdMapInterface::STATUS_IGNORED];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of items that failed.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getFailedCount() {
|
||||
return $this->saveCounters[MigrateIdMapInterface::STATUS_FAILED];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of items processed. Note that STATUS_NEEDS_UPDATE
|
||||
* is not counted, since this is typically set on stubs created as side
|
||||
* effects, not on the primary item being imported.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getProcessedCount() {
|
||||
return $this->saveCounters[MigrateIdMapInterface::STATUS_IMPORTED] +
|
||||
$this->saveCounters[MigrateIdMapInterface::STATUS_NEEDS_UPDATE] +
|
||||
$this->saveCounters[MigrateIdMapInterface::STATUS_IGNORED] +
|
||||
$this->saveCounters[MigrateIdMapInterface::STATUS_FAILED];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of items rolled back.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRollbackCount() {
|
||||
return $this->deleteCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all the per-status counters to 0.
|
||||
*/
|
||||
protected function resetCounters() {
|
||||
foreach ($this->saveCounters as $status => $count) {
|
||||
$this->saveCounters[$status] = 0;
|
||||
}
|
||||
$this->deleteCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* React to migration completion.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The map event.
|
||||
*/
|
||||
public function onPostImport(MigrateImportEvent $event) {
|
||||
$migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported');
|
||||
$migrate_last_imported_store->set($event->getMigration()->id(), round(microtime(TRUE) * 1000));
|
||||
$this->progressMessage();
|
||||
$this->removeListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all our event listeners.
|
||||
*/
|
||||
protected function removeListeners() {
|
||||
foreach ($this->listeners as $event => $listener) {
|
||||
\Drupal::service('event_dispatcher')->removeListener($event, $listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit information on what we've done since the last feedback (or the
|
||||
* beginning of this migration).
|
||||
*
|
||||
* @param bool $done
|
||||
*/
|
||||
protected function progressMessage($done = TRUE) {
|
||||
$processed = $this->getProcessedCount();
|
||||
if ($done) {
|
||||
$singular_message = "Processed 1 item (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
|
||||
$plural_message = "Processed @numitems items (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
|
||||
}
|
||||
else {
|
||||
$singular_message = "Processed 1 item (@created created, @updated updated, @failures failed, @ignored ignored) - continuing with '@name'";
|
||||
$plural_message = "Processed @numitems items (@created created, @updated updated, @failures failed, @ignored ignored) - continuing with '@name'";
|
||||
}
|
||||
$this->message->display(\Drupal::translation()->formatPlural($processed,
|
||||
$singular_message, $plural_message,
|
||||
['@numitems' => $processed,
|
||||
'@created' => $this->getCreatedCount(),
|
||||
'@updated' => $this->getUpdatedCount(),
|
||||
'@failures' => $this->getFailedCount(),
|
||||
'@ignored' => $this->getIgnoredCount(),
|
||||
'@name' => $this->migration->id()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* React to rollback completion.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The map event.
|
||||
*/
|
||||
public function onPostRollback(MigrateRollbackEvent $event) {
|
||||
$this->rollbackMessage();
|
||||
$this->removeListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit information on what we've done since the last feedback (or the
|
||||
* beginning of this migration).
|
||||
*
|
||||
* @param bool $done
|
||||
*/
|
||||
protected function rollbackMessage($done = TRUE) {
|
||||
$rolled_back = $this->getRollbackCount();
|
||||
if ($done) {
|
||||
$singular_message = "Rolled back 1 item - done with '@name'";
|
||||
$plural_message = "Rolled back @numitems items - done with '@name'";
|
||||
}
|
||||
else {
|
||||
$singular_message = "Rolled back 1 item - continuing with '@name'";
|
||||
$plural_message = "Rolled back @numitems items - continuing with '@name'";
|
||||
}
|
||||
$this->message->display(\Drupal::translation()->formatPlural($rolled_back,
|
||||
$singular_message, $plural_message,
|
||||
['@numitems' => $rolled_back,
|
||||
'@name' => $this->migration->id()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* React to an item about to be imported.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
|
||||
* The pre-save event.
|
||||
*/
|
||||
public function onPreRowSave(MigratePreRowSaveEvent $event) {
|
||||
$id_map = $event->getRow()->getIdMap();
|
||||
if (!empty($id_map['destid1'])) {
|
||||
$this->preExistingItem = TRUE;
|
||||
}
|
||||
else {
|
||||
$this->preExistingItem = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* React to item rollback.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
|
||||
* The post-save event.
|
||||
*/
|
||||
public function onPostRowDelete(MigrateRowDeleteEvent $event) {
|
||||
if ($this->feedback && ($this->deleteCounter) && $this->deleteCounter % $this->feedback == 0) {
|
||||
$this->rollbackMessage(FALSE);
|
||||
$this->resetCounters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* React to a new row.
|
||||
*
|
||||
* @param \Drupal\migrate_plus\Event\MigratePrepareRowEvent $event
|
||||
* The prepare-row event.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||
*/
|
||||
public function onPrepareRow(MigratePrepareRowEvent $event) {
|
||||
if (!empty($this->idlist)) {
|
||||
$row = $event->getRow();
|
||||
/**
|
||||
* @TODO replace for $source_id = $row->getSourceIdValues(); when https://www.drupal.org/node/2698023 is fixed
|
||||
*/
|
||||
$migration = $event->getMigration();
|
||||
$source_id = array_merge(array_flip(array_keys($migration->getSourcePlugin()
|
||||
->getIds())), $row->getSourceIdValues());
|
||||
$skip = TRUE;
|
||||
foreach ($this->idlist as $item) {
|
||||
if (array_values($source_id) == $item) {
|
||||
$skip = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($skip) {
|
||||
throw new MigrateSkipRowException(NULL, FALSE);
|
||||
}
|
||||
}
|
||||
if ($this->feedback && ($this->counter) && $this->counter % $this->feedback == 0) {
|
||||
$this->progressMessage(FALSE);
|
||||
$this->resetCounters();
|
||||
}
|
||||
$this->counter++;
|
||||
if ($this->itemLimit && ($this->getProcessedCount() + 1) >= $this->itemLimit) {
|
||||
$event->getMigration()->interruptMigration(MigrationInterface::RESULT_COMPLETED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -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: { }
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies: { }
|
||||
id: default
|
||||
label: Default
|
||||
description: ''
|
||||
source_type: ''
|
||||
module: null
|
||||
shared_configuration: null
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
type: module
|
||||
name: Migrate Tools Test
|
||||
description: 'Test module to test Migrate Tools.'
|
||||
package: Testing
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:migrate (>=8.3)
|
||||
- migrate_plus:migrate_plus
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-17
|
||||
version: '8.x-4.0-beta1+5-dev'
|
||||
core: '8.x'
|
||||
project: 'migrate_tools'
|
||||
datestamp: 1510918687
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate_tools\Functional;
|
||||
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Execution form test
|
||||
*
|
||||
* @group migrate_tools
|
||||
*/
|
||||
class MigrateExecutionFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user',
|
||||
'filter',
|
||||
'field',
|
||||
'node',
|
||||
'text',
|
||||
'taxonomy',
|
||||
'migrate',
|
||||
'migrate_plus',
|
||||
'migrate_tools',
|
||||
'migrate_tools_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* The vocabulary.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyInterface
|
||||
*/
|
||||
protected $vocabulary;
|
||||
|
||||
/**
|
||||
* The vocabulary query.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
protected $vocabularyQuery;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->vocabulary = $this->createVocabulary(['vid' => 'fruit', 'name' => 'Fruit']);
|
||||
$this->vocabularyQuery = $this->container->get('entity_type.manager')
|
||||
->getStorage('taxonomy_term')
|
||||
->getQuery();
|
||||
// Log in as user 1. Migrations in the UI can only be performed as user 1.
|
||||
$this->drupalLogin($this->rootUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests execution of import and rollback of a migration.
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ExpectationException
|
||||
*/
|
||||
public function testExecution() {
|
||||
$group = 'default';
|
||||
$migration = 'fruit_terms';
|
||||
$urlPath = "/admin/structure/migrate/manage/{$group}/migrations/{$migration}/execute";
|
||||
$real_count = $this->vocabularyQuery->count()->execute();
|
||||
$expected_count = 0;
|
||||
$this->assertEquals($expected_count, $real_count);
|
||||
$this->drupalGet($urlPath);
|
||||
$this->assertSession()->responseContains('Choose an operation to run');
|
||||
$edit = [
|
||||
'operation' => 'import',
|
||||
];
|
||||
$this->drupalPostForm($urlPath, $edit, t('Execute'));
|
||||
$real_count = $this->vocabularyQuery->count()->execute();
|
||||
$expected_count = 3;
|
||||
$this->assertEquals($expected_count, $real_count);
|
||||
$edit = [
|
||||
'operation' => 'rollback',
|
||||
];
|
||||
$this->drupalPostForm($urlPath, $edit, t('Execute'));
|
||||
$real_count = $this->vocabularyQuery->count()->execute();
|
||||
$expected_count = 0;
|
||||
$this->assertEquals($expected_count, $real_count);
|
||||
$edit = [
|
||||
'operation' => 'import',
|
||||
];
|
||||
$this->drupalPostForm($urlPath, $edit, t('Execute'));
|
||||
$real_count = $this->vocabularyQuery->count()->execute();
|
||||
$expected_count = 3;
|
||||
$this->assertEquals($expected_count, $real_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom vocabulary based on default settings.
|
||||
*
|
||||
* @param array $values
|
||||
* An array of settings to change from the defaults.
|
||||
* Example: 'vid' => 'foo'.
|
||||
*
|
||||
* @return \Drupal\taxonomy\VocabularyInterface
|
||||
* Created vocabulary.
|
||||
*/
|
||||
protected function createVocabulary(array $values = []) {
|
||||
// Find a non-existent random vocabulary name.
|
||||
if (!isset($values['vid'])) {
|
||||
do {
|
||||
$id = strtolower($this->randomMachineName(8));
|
||||
} while (Vocabulary::load($id));
|
||||
}
|
||||
else {
|
||||
$id = $values['vid'];
|
||||
}
|
||||
$values += [
|
||||
'id' => $id,
|
||||
'name' => $id,
|
||||
];
|
||||
$vocabulary = Vocabulary::create($values);
|
||||
$status = $vocabulary->save();
|
||||
|
||||
$this->assertSame($status, SAVED_NEW);
|
||||
|
||||
return $vocabulary;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user