upgraded contrib modules

This commit is contained in:
Bachir Soussi Chiadmi
2017-11-21 12:03:46 +01:00
parent b32fe9ef14
commit 38f62621b1
217 changed files with 16074 additions and 217 deletions
@@ -0,0 +1,293 @@
Admin Toolbar 8.1.21, 2017-11-20
--------------------------------
Changes since 8.1.20:
- #2923580 by sylus, adriancid: Unsupported operand types in
ToolbarHandler::lazyBuilder().
- #2731369 by stefan.r, DuneBL, Berdir, kbasarab, joachim, adriancid: Local
tasks don't show in the toolbar.
- #2920793 by adriancid: Change the user TAKTAK by matio89 in the CHANGELOG.txt
file.
- #2873228 by smustgrave, adriancid, flocondetoile: Toolbar menu accessible and
navigable with keyboard.
- #2919775 by adriancid: Create the CHANGELOG.txt file.
- #2919772 by adriancid: Use the README.txt template.
- #2919769 by adriancid: Use the README.txt template for the admin_toolbar_tools
submodule.
- #2897309 by finne, adriancid, eme, rgpublic: admin_toolbar_tools module
makes all pages uncacheable.
- #2913299 by esod, Chi, adriancid, hudri, eme: CSS Z-Index of toolbar is
inappropriate.
- #2919390 by adriancid: Create the README.txt file for the admin toolbar links
access filter submodule.
- #2919367 by adriancid: Fix coding standard format.
- #2919346 by adriancid: Don't show menu links that you don't have access
permission for.
- #2917710: Create the .info.yml file for the admin_toolbar_links_access_filter
submodule.
- #2916064 by adriancid, eme, finne: Use in drupal.org links url parameter and
not route_name.
- #2916040 by adriancid, finne, eme: Remove unused route admin_development.
- #2915778 by finne, adriancid: Remove the _csrf_token from routes that don't
need it.
- #2909359 by adriancid, Musa.thomas: Add a menu link to clean the Views cache.
- #2910931 by adriancid: .info.yml files don't have the drupal version.
- #2759135 by Johnny vd Laar, miiimooo, romainj, chegor, adriancid, ckaotik,
bdominguez, SpadXIII, mrtndlmt: Content type, Vocabularies and Menus names are
not translated.
- #2909710 by adriancid, Musa.thomas: Fix coding standard format.
- #2910934 by adriancid, romainj: Error trying to clean the cache.
- #2910892 by adriancid: Delete the LICENSE.txt.
- #2909637 by bapi_22, Musa.thomas, adriancid: Avoid static call inside class
method.
- #2909003 by eme: fix z-index for CKEditor.
- #2504449 by robin.ingelbrecht, rwam, eme: Un-hover delay.
Admin Toolbar 8.1.20, 2017-09-07
--------------------------------
Changes since 8.1.19:
- #2887439 by romainj: Fix test.
- #2759135 by Johnny vd Laar, miiimooo, chegor, romainj: Content type,
Vocabularies and Menus names are not translated.
- #2894520 by romainj, bapi_22: Remove deprecated constant REQUEST_TIME from
code base.
- #2883098 by romainj, Alka Kumari: Admin Toolbar Extra Tools module display
theme machine names instead of labels.
- #2504449 by robin.ingelbrecht: Un-hover delay.
- #2838636 by ddrozdik, Fons Vandamme, kerby70, romainj: Issue with z-index of
dropdown in horizontal tray.
- #2838636 by Fons Vandamme, ddrozdik, romainj: Issue with z-index of dropdown
in horizontal tray.
- #2870404 by romainj: Add a link to the Webprofiler settings page.
- #2706643 by esod, romainj, Keenegan, DamienMcKenna: Use short array syntax.
Admin Toolbar 8.1.19, 2017-04-06
--------------------------------
Changes since 8.1.18:
- #2706643 by esod, romainj, Keenegan, DamienMcKenna: Use short array syntax.
- #2781745 by minakshiPh, eelkeblok, romainj, akhilavnair_zyxware: Drupal coding
standard issues found in most of the files.
- #2855720 by stefan.r, romainj: No logout link in admin toolbar anymore.
- #2841512 by Chi, romainj: User error: Redirects to external URLs are not
allowed by default.
Admin Toolbar 8.1.18, 2016-12-01
--------------------------------
Changes since 8.1.17:
- #2830677 by vaplas, romainj: z-index for sub menu.
- by romainj: Get rid of the Hello popup.
- #2805431 by marcusx: Accidentaly committed alert.
- #2630724 by squarecandy, romainj, reblutus, Hemangi Gokhale, jacoferg,
Balu Ertl, Jeff Burnz, Don Greco: Consider changes to link title parameter to
avoid tooltip visual conflict.
- #2518202 by james.williams, chegor, eme, romainj, DuneBL: Change default link
to taxonomy.
Admin Toolbar 8.1.17, 2016-08-19
--------------------------------
Changes since 8.1.16:
- #2779251 by romainj, DuneBL: Wrong integration of field_collection delete
form.
- #2776229 by Chi, romainj, mattlt: Chevron icons missing if Drupal is not in
document root.
- by romainj: CSS coding standard cleaning.
- #2781059 by mstrelan, romainj: Why is CRON in all caps?
- #2778935 by akhilavnair_zyxware, rjarraud: As per Drupal Standards each line
in documentation should not exceeds 80 characters.
- by romainj: Adds ending new line where required.
- #2776229 by Chi, rjarraud: Chevron icons missing if Drupal is not in document
root.
- by eme: Commiting again #2707611.
- #2757687 by neerajsingh: Remove usages of \Drupal::url().
- #2707747 by Balu Ertl, nevergone, eme, esod: D8 logo not rendered with smooth
antialiasing.
Admin Toolbar 8.1.16, 2016-07-28
--------------------------------
Changes since 8.1.15:
- #2723209 by andrewmacpherson, colan, mattshoaf: Enabling admin_toolbar_tools
causes Devel Settings to disappear from the main configuration page.
- #2759335 bycwells, jalpesh: fixes a mistaken link in hook_help().
- #2664564 by kolier, matio89, romainj and Michèle: auto-detect content entities
with Field UI route.
- #2735257 by colan, romainj, Balu Ertl: respect of coding standards in the
module README.txt file.
- #2735257 by colan, romainj, Balu Ertl: shortens the module README.txt
description text.
- by romainj: Fixes a problem with the link to the Admin menu overview page.
- #2735257 by Balu Ertl, colan, romainj: improves Admin Toolbar Extra Tools
description in the README.txt file.
- #2723381 by joachim, romainj: fieldable content entities are no more
hardcoded.
- #2735257 by Balu Ertl, colan, romainj: added some description details in the
README.txt files.
- by romainj: Menu link definitions updated.
- by romainj: Change routing permissions to follow Drupal default permissions
for administration pages.
- by romainj: Fixes issue #2737027.
- by romainj: fixes issue #2701825.
- by romainj: Adds the menu name for each menu item in
admin_toolbar_tools/admin_toolbar_tools.links.menu.yml file.
- #2735153 by Kionn: InvalidArgumentException: Cannot redirect to an empty URL.
в Symfony\Component\HttpFoundation\RedirectResponse->setTargetUrl().
- by matio89: correction InvalidArgumentException: Cannot redirect to an empty
URL. в Symfony\Component\HttpFoundation\RedirectResponse->setTargetUrl().
- by romainj: Add a new Render Cache shortcut on the admin menu.
- by romainj: Fixes issue #2731663 by canceling the move of the Logout menu
link.
Admin Toolbar 8.1.15, 2016-05-23
--------------------------------
Changes since 8.1.14:
- by matio89: add RTL in admin.toolbar.css.
- by romainj: Changes Devel menu item titles.
- by romainj: Fixes issue #2713899.
- by romainj: Minor code/typo cleaning.
- #2707611 by Balu Ertl: Add fine shadow under dropdown menus.
- #2707789 by esod: Fix Automated Tests.
- by romainj: Code cleaning.
- by romainj: Replace the deprecated entityManager() by entityTypeManager()
service.
- by romainj: Emptying menu cache with all link types.
- #2614962 by dbt102, seppelM, neha.gangwar, chegor: Correct text in
info.yml(s).
- by eme: Minor typo changes.
- by eme: Fix dependencies to core modules.
- by eme: Fix issue #2666964.
- by eme: Fix css background color level3.
- by eme: fix issue #2658896 and refactor and fix chevron-right.svg.
- by eme: fix Issue #2493037.
Admin Toolbar 8.1.14, 2016-02-08
--------------------------------
Changes since 8.1.13:
- by matio89: Correction differents bugs(problem white page when installing a
new module).
- #2643648 by Lord_of_Codes: Proposed README.txt file for Admin Toolbar.
- by matio89: resolving the dependecies problem(dependencies of others modules).
- by matio89: Correction route of entities.
Admin Toolbar 8.1.13, 2016-02-02
--------------------------------
Changes since 8.1.12:
- by matio89: Admin toolbar is compatible with 8.0.2.
- by matio89: Commmit dev version compatible with drupal 8.0.2.
- by matio89: New version of admin toolbar compatible with 8.0.2.
Admin Toolbar 8.1.12, 2016-01-15
--------------------------------
Changes since 8.1.11:
- by matio89: Correction AdminToolbaToolsAlterTest.
- #2493037 by mimran: Empty elements in #toolbar-bar.
- #2598136 by mimran, felribeiro: Menu items duplicated.
- #2635154 by Lukas von Blarer: Too general CSS selectors.
- by eme: Fix chevron right in local state.
Admin Toolbar 8.1.11, 2015-12-11
--------------------------------
Changes since 8.1.10:
- #2620430 by Vagelis, NarendraR: Typo.
- #2632888 by JamesK: Add dependency on node module.
- by eme: Fix admin toolbar test.
- #2552081 by jonhattan, bruvers: chevron-right.svg not found.
- by Vagelis: Minor typos.
- by eme: Refactor basic tests.
- #2613378 by chegor: Add basic test.
- #2612694 by chegor: Add new view.
- #2627918 by joe_carvajal: Version in info.yml files does Update Manager try to
update.
- by matio89: correction collapsible behaviour in vertical menu layout.
- #2582825 by Ben Coleman: Installing Admin Toolbar Tools loses anonymous user
login link.
- by matio89: Correction the login link on the site when in a logged-out state.
Admin Toolbar 8.1.10, 2015-08-07
--------------------------------
Changes since 8.1.9:
- #2546939 by jonhattan: Add dependency on toolbar module.
Admin Toolbar 8.1.9, 2015-07-22
-------------------------------
Changes since 8.1.8:
- #2537016 by matio89: Removing dependance for user & system.
- #2533420 by Dave Reid, twistor: Chase HEAD changes in toolbar.
- #2537016 by twistor: Rewrite admin_toolbar_tools.module
- by matio89: Correction all bugs and add new security features.
Admin Toolbar 8.1.8, 2015-06-22
-------------------------------
Changes since 8.1.7:
- correction not found route.
- #2493037 by bobrov1989: Empty elements in #toolbar-bar.
- by fethi: icones.
Admin Toolbar 8.1.7, 2015-06-22
-------------------------------
Changes since 8.1.6:
- correction not found route.
- #2493037 by bobrov1989: Empty elements in #toolbar-bar.
- by fethi: icones.
Admin Toolbar 8.1.6, 2015-06-03
-------------------------------
Changes since 8.1.5:
- by matio89: correction admin_toolbar.
Admin Toolbar 8.1.5, 2015-06-03
-------------------------------
Changes since 8.1.4:
- by matio89: test if module update exist or no to display install and update
module.
- by fethi: Change the colors used to match the Seven styleguide.
- by matio89: compatibility with Drupal 8 béta 11.
- by fethi: Change the colors used to match the Seven styleguide.
Admin Toolbar 8.1.4, 2015-05-20
-------------------------------
Changes since 8.1.3:
- by matio89: correction add/node.
Admin Toolbar 8.1.3, 2015-05-19
-------------------------------
Changes since 8.1.2:
- by matio89: add new functionalities.
- by matio89: add administration devel link.
- by matio89: correction reload page.
- by matio89: correction redirect to the same page.
Admin Toolbar 8.1.2, 2015-05-19
-------------------------------
Changes since 8.1.1:
- by matio89: add new functionalities.
- by matio89: add administration devel link.
- by matio89: correction reload page.
- by matio89: correction redirect to the same page.
Admin Toolbar 8.1.1, 2015-05-19
-------------------------------
Changes since 8.1.0:
- by matio89: add new functionalities.
Admin Toolbar 8.1.0, 2015-05-07
-------------------------------
- Initial release.
@@ -1,45 +1,69 @@
---SUMMARY---
CONTENTS OF THIS FILE
---------------------
Admin Toolbar intends to improve the default Drupal Toolbar to transformit into
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
Admin Toolbar intends to improve the default Drupal Toolbar to transform it into
a drop-down menu, providing a fast and full access to all administration links.
For a full description visit project page:
https://www.drupal.org/project/admin_toolbar
* For a full description of the module, visit the project page:
https://www.drupal.org/project/admin_toolbar
Bug reports, feature suggestions and latest developments:
http://drupal.org/project/issues/admin_toolbar
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/search/admin_toolbar
---REQUIREMENTS---
REQUIREMENTS
------------
No special requirements.
*None. (Other than a clean Drupal 8 installation)
RECOMMENDED MODULES
-------------------
* Admin Toolbar Extra Tools (https://www.drupal.org/project/admin_toolbar):
Provides menu links to administration pages or actions (eg. Flushing caches)
that are not generated by Drupal core.
* Admin Toolbar Links Access Filter (https://www.drupal.org/node/2474539):
Provides a workaround for the common problem that users with
'Use the administration pages and help' permission see menu links they don't
have access permission for.
INSTALLATION
------------
* Install as you would normally install a contributed Drupal module.
See: https://www.drupal.org/node/895232 for further information.
---INSTALLATION---
CONFIGURATION
-------------
No configuration is needed.
Install as usual.
Place the entirety of this directory in the /modules folder of your Drupal
installation. Navigate to Administer > Extend. Check the 'Enabled' box next
to the 'Admin toolbar' and/or 'Admin toolbar Extra Tools' and then click
the 'Save Configuration' button at the bottom.
For help regarding installation, visit:
https://www.drupal.org/documentation/install/modules-themes/modules-8
---CONTACT---
Current Maintainers:
*Wilfrid Roze (eme) - https://www.drupal.org/u/eme
*Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
MAINTAINERS
-----------
Current maintainers:
* Wilfrid Roze (eme) - https://www.drupal.org/u/eme
* Romain Jarraud (romainj) - https://www.drupal.org/u/romainj
* Adrian Cid Almaguer (adriancid) - https://www.drupal.org/u/adriancid
* Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
This project has been sponsored by:
*emerya
Founded in 2009, emerya is a human-sized company, dedicated to the design
and implementation of web interfaces.
Visit: http://http://emerya.fr/ for more information.
* emerya
Founded in 2009, emerya is a human-sized company, dedicated to the design and
implementation of web interfaces. Visit: http://http://emerya.fr/ for more
information.
@@ -1,13 +1,15 @@
name: Admin Toolbar
# core: 8.x
description: Provides a drop-down menu interface to the core Drupal Toolbar.
package: Administration
type: module
# core: 8.x
dependencies:
- toolbar
# Information added by Drupal.org packaging script on 2017-09-12
version: '8.x-1.20'
# Information added by Drupal.org packaging script on 2017-11-20
version: '8.x-1.21'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1505199847
datestamp: 1511190792
@@ -23,9 +23,15 @@ function admin_toolbar_toolbar_alter(&$items) {
function admin_toolbar_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.admin_toolbar':
$variables = [
':toolbar' => Url::fromRoute('help.page', ['name' => 'toolbar'])->toString(),
':automated_cron' => (\Drupal::moduleHandler()->moduleExists('automated_cron')) ? Url::fromRoute('help.page', ['name' => 'automated_cron'])->toString() : '#',
':admin_toolbar_documentation' => 'https://www.drupal.org/node/2713693',
];
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Admin Toolbar module enhances the <a href=":toolbar">Toolbar</a> module by providing fast access to all the administrative links at the top of your site. Admin Toolbar remains a very "lightweight" module by closely integrating with all Toolbar functionality. It can be used in conjunction with all the sub or complimentary modules, listed on <a href="https://www.drupal.org/project/admin_toolbar">Admin Toolbar</a>, for quick access to system commands such as Flush all caches, <a href=":automated_cron">Run cron</a>, Run Updates, etc... For more information, see <a href=":admin_toolbar_documentation">the online documentation for the Admin Toolbar module</a>.', [':toolbar' => Url::fromRoute('help.page', ['name' => 'toolbar'])->toString(), ':automated_cron' => (\Drupal::moduleHandler()->moduleExists('automated_cron')) ? Url::fromRoute('help.page', ['name' => 'automated_cron'])->toString() : '#', ':admin_toolbar_documentation' => 'https://www.drupal.org/node/2713693']) . '</p>';
$output .= '<p>' . t('The Admin Toolbar module enhances the <a href=":toolbar">Toolbar</a> module by providing fast access to all the administrative links at the top of your site. Admin Toolbar remains a very "lightweight" module by closely integrating with all Toolbar functionality. It can be used in conjunction with all the sub or complimentary modules, listed on <a href="https://www.drupal.org/project/admin_toolbar">Admin Toolbar</a>, for quick access to system commands such as Flush all caches, <a href=":automated_cron">Run cron</a>, Run Updates, etc... For more information, see <a href=":admin_toolbar_documentation">the online documentation for the Admin Toolbar module</a>.', $variables) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<p>' . t('The Admin Toolbar greatly improves the user experience for those who regularly interact with the Drupal Toolbar by providing fast, full access to all links in the Drupal Toolbar without having to click to get there.') . '</p>';
@@ -83,7 +89,8 @@ function toolbar_tools_menu_navigation_links(array $tree) {
$definition = $link->getPluginDefinition();
$element->options['attributes']['class'][] = 'toolbar-icon';
$element->options['attributes']['class'][] = 'toolbar-icon-' . strtolower(str_replace(['.', ' ', '_'], ['-', '-', '-'], $definition['id']));
$string = strtolower(str_replace(['.', ' ', '_'], ['-', '-', '-'], $definition['id']));
$element->options['attributes']['class'][] = 'toolbar-icon-' . $string;
$element->options['attributes']['title'] = $link->getDescription();
}
return $tree;
@@ -0,0 +1,61 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Admin Toolbar Links Access Filter module Provides a workaround for the
common problem that users with 'Use the administration pages and help'
permission see menu links they don't have access permission for. Once the issue
https://www.drupal.org/node/296693 be solved, this module will be deprecated.
* To know why we create this module check this issue:
https://www.drupal.org/node/2917704
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/search/admin_toolbar
REQUIREMENTS
------------
No special requirements.
RECOMMENDED MODULES
-------------------
* Admin Toolbar (https://www.drupal.org/project/admin_toolbar):
Improve the default Drupal Toolbar (the administration menu at the top of
your site) to transform it into a drop-down menu, providing a fast access to
all administration pages.
INSTALLATION
------------
* Install as you would normally install a contributed Drupal module.
See: https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
No configuration is needed.
MAINTAINERS
-----------
Current maintainers:
* Wilfrid Roze (eme) - https://www.drupal.org/u/eme
* Romain Jarraud (romainj) - https://www.drupal.org/u/romainj
* Adrian Cid Almaguer (adriancid) - https://www.drupal.org/u/adriancid
@@ -0,0 +1,12 @@
name: Admin Toolbar Links Access Filter
description: Provides a workaround for the common problem that users with 'Use the administration pages and help' permission see menu links they don't have access permission for. Once the issue <a href='https://www.drupal.org/node/296693'>https://www.drupal.org/node/296693</a> be solved, this module will be deprecated.
package: Administration
type: module
# core: 8.x
# Information added by Drupal.org packaging script on 2017-11-20
version: '8.x-1.21'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1511190792
@@ -0,0 +1,176 @@
<?php
/**
* @file
* This module don't show menu links that you don't have access permission for.
*/
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* Implements hook_preprocess_menu().
*
* Hides links from admin menu, if user doesn't have access rights.
*/
function admin_toolbar_links_access_filter_preprocess_menu(&$variables) {
if (empty($variables['items'])) {
// Additional empty check to prevent exotic situations, where the preprocess
// function is entered even without items.
// @see https://www.drupal.org/node/2833885
return;
}
// Ensure that menu_name exists.
if (!isset($variables['menu_name'])) {
// In rare cases (for unknown reasons) menu_name may not be set.
// As fallback, we can fetch it from the first menu item.
$first_link = reset($variables['items']);
/** @var Drupal\Core\Menu\MenuLinkDefault $original_link */
// Fetch the menu_name from the original link.
$original_link = $first_link['original_link'];
$variables['menu_name'] = $original_link->getMenuName();
}
if ($variables['menu_name'] == 'admin') {
if (!admin_toolbar_links_access_filter_user_has_admin_role($variables['user'])) {
admin_toolbar_links_access_filter_filter_non_accessible_links($variables['items']);
}
}
}
/**
* Hides links from admin menu, if user doesn't have access rights.
*/
function admin_toolbar_links_access_filter_filter_non_accessible_links(array &$items) {
foreach ($items as $route => &$item) {
$route_name = $route;
$route_params = [];
if (!empty($item['original_link'])) {
/** @var \Drupal\Core\Menu\MenuLinkBase $original_link */
$original_link = $item['original_link'];
if ($original_link->getUrlObject()->isExternal()) {
// Do not filter external URL at all.
continue;
}
$route_name = $original_link->getRouteName();
$route_params = $original_link->getRouteParameters();
}
// Check, if user has access rights to the route.
if (!\Drupal::accessManager()->checkNamedRoute($route_name, $route_params)) {
unset($items[$route]);
}
else {
if (!empty($items[$route]['below'])) {
// Recursively call this function for the child items.
admin_toolbar_links_access_filter_filter_non_accessible_links($items[$route]['below']);
}
if (empty($items[$route]['below']) && \Drupal::moduleHandler()->moduleExists('admin_toolbar')) {
// Every child item has been cleared out.
// Now check, if the given route represents an overview page only,
// without having functionality on its own. In this case, we can safely
// unset this item, as there aren't any children left.
// This assumption is only valid, when the admin_toolbar module is
// installed because otherwise we won't have child items at all.
if (admin_toolbar_links_access_filter_is_overview_page($route)) {
unset($items[$route]);
}
else {
// Let's remove the expanded flag.
$items[$route]['is_expanded'] = FALSE;
}
}
}
}
}
/**
* Implements template_preprocess_admin_block_content().
*/
function admin_toolbar_links_access_filter_admin_block_content(&$variables) {
if (!admin_toolbar_links_access_filter_user_has_admin_role($variables['user'])) {
foreach ($variables['content'] as $key => &$item) {
if (isset($item['url']) && $item['url'] instanceof Url) {
/* @var \Drupal\Core\Url $url */
$url = $item['url'];
if ($url->access()) {
continue;
}
unset($variables['content'][$key]);
}
// The key is structured in the form: "ID title route",
// concatenated with spaces.
$key_parts = explode(' ', $key);
$route = end($key_parts);
// Special handling for Views pages, as they are not defined
// system routes.
// @TODO check the permission for Views + find a generic way for similar
// cases. Best way would be to get the link entity somehow to properly
// check permissions.
if (strpos($route, 'views_view:') === 0) {
continue;
}
// Check, if user has access rights to the route.
if (!\Drupal::accessManager()->checkNamedRoute($route)) {
unset($variables['content'][$key]);
}
}
}
}
/**
* Checks if the given route name is an overview page.
*
* Checks if the given route name matches a pure (admin) overview page that can
* be skipped, if there are no child items set. The typical example are routes
* having the SystemController::systemAdminMenuBlockPage() function as their
* controller callback set.
*
* @param string $route_name
* The route name to check.
*
* @return bool
* TRUE, if the given route name matches a pure admin overview page route,
* FALSE otherwise.
*/
function admin_toolbar_links_access_filter_is_overview_page($route_name) {
// @var \Drupal\Core\Routing\RouteProviderInterface $route_provider.
$route_provider = \Drupal::service('router.route_provider');
$overview_page_controllers = [
'\Drupal\system\Controller\AdminController::index',
'\Drupal\system\Controller\SystemController::overview',
'\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage',
];
try {
$route = $route_provider->getRouteByName($route_name);
$controller = $route->getDefault('_controller');
return !empty($controller) && in_array($controller, $overview_page_controllers);
}
catch (RouteNotFoundException $ex) {
}
return FALSE;
}
/**
* Checks, if the given user has admin rights.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check.
*
* @return bool
* TRUE, if the given user account has at least one role with admin rights
* assigned, FALSE otherwise.
*/
function admin_toolbar_links_access_filter_user_has_admin_role(AccountInterface $account) {
foreach ($account->getRoles() as $role_id) {
if (Role::load($role_id)->isAdmin()) {
return TRUE;
}
}
return FALSE;
}
@@ -1,45 +1,71 @@
---SUMMARY---
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
Admin Toolbar Extra Tools provides menu links to administration pages or actions
(eg. Flushing caches) that are not generated by Drupal core. It adds menu items
that are not generated by Drupal core. For example there are no menu items for
each content type by default or menu links to Manage fields on each entity types.
That's the purpose of Admin Toolbar Extra Tools to add them.
each content type by default or menu links to Manage fields on each entity
types. That's the purpose of Admin Toolbar Extra Tools to add them.
For a full description visit project page:
https://www.drupal.org/project/admin_toolbar
* For a full description of the module, visit the project page:
https://www.drupal.org/project/admin_toolbar
Bug reports, feature suggestions and latest developments:
http://drupal.org/project/issues/admin_toolbar
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/search/admin_toolbar
---REQUIREMENTS---
REQUIREMENTS
------------
*Admin Toolbar.
This module requires the following modules:
* Admin Toolbar (https://www.drupal.org/project/admin_toolbar)
---INSTALLATION---
RECOMMENDED MODULES
-------------------
Install as usual.
Place the entirety of this directory in the /modules folder of your Drupal
installation. Navigate to Administer > Extend. Check the 'Enabled' box next
'Admin toolbar Extra Tools' and then click the 'Save Configuration' button at
the bottom.
For help regarding installation, visit:
https://www.drupal.org/documentation/install/modules-themes/modules-8
* Admin Toolbar (https://www.drupal.org/project/admin_toolbar):
Improve the default Drupal Toolbar (the administration menu at the top of
your site) to transform it into a drop-down menu, providing a fast access to
all administration pages.
---CONTACT---
INSTALLATION
------------
Current Maintainers:
*Wilfrid Roze (eme) - https://www.drupal.org/u/eme
*Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
* Install as you would normally install a contributed Drupal module.
See: https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
No configuration is needed.
MAINTAINERS
-----------
Current maintainers:
* Wilfrid Roze (eme) - https://www.drupal.org/u/eme
* Romain Jarraud (romainj) - https://www.drupal.org/u/romainj
* Adrian Cid Almaguer (adriancid) - https://www.drupal.org/u/adriancid
* Mohamed Anis Taktak (matio89) - https://www.drupal.org/u/matio89
This project has been sponsored by:
*emerya
Founded in 2009, emerya is a human-sized company, dedicated to the design and
implementation of web interfaces.
Visit: http://http://emerya.fr/ for more information.
* emerya
Founded in 2009, emerya is a human-sized company, dedicated to the design and
implementation of web interfaces. Visit: http://http://emerya.fr/ for more
information.
@@ -1,13 +1,15 @@
name: Admin Toolbar Extra Tools
# core: 8.x
description: Adds menu links to the Admin Toolbar.
package: Administration
type: module
# core: 8.x
dependencies:
- admin_toolbar
# Information added by Drupal.org packaging script on 2017-09-12
version: '8.x-1.20'
# Information added by Drupal.org packaging script on 2017-11-20
version: '8.x-1.21'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1505199847
datestamp: 1511190792
@@ -12,13 +12,6 @@ system.admin_index:
parent: admin_toolbar_tools.help
weight: -100
system.run_cron:
title: 'Run cron'
route_name: admin_toolbar.run.cron
menu_name: admin
parent: admin_toolbar_tools.help
weight: -8
system.db_update:
title: 'Run updates'
route_name: system.db_update
@@ -32,60 +25,23 @@ system.modules_uninstall:
menu_name: admin
parent: system.modules_list
admin_toolbar_tools.flush:
title: 'Flush all caches'
route_name: admin_toolbar_tools.flush
weight: -9
parent: admin_toolbar_tools.help
menu_name: admin
admin_toolbar_tools.cssjs:
title: 'Flush CSS and Javascript'
route_name: admin_toolbar_tools.cssjs
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.plugin:
title: 'Flush plugins cache'
route_name: admin_toolbar_tools.plugin
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.flush_static:
title: 'Flush static cache'
route_name: admin_toolbar_tools.flush_static
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.flush_menu:
title: 'Flush routing and links cache'
route_name: admin_toolbar_tools.flush_menu
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.flush_rendercache:
title: 'Flush render cache'
route_name: admin_toolbar_tools.flush_rendercache
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.drupalorg:
title: 'Drupal.org'
weight: -5
route_name: admin_toolbar_tools.drupalorg
url: https://www.drupal.org
parent: admin_toolbar_tools.help
menu_name: admin
admin_toolbar_tools.listchanges:
title: 'Change records for Drupal core'
weight: -6
route_name: admin_toolbar_tools.listchanges
url: https://www.drupal.org/list-changes
parent: admin_toolbar_tools.drupalorg
menu_name: admin
admin_toolbar_tools.doc:
title: 'D8 API documentation'
weight: -5
route_name: admin_toolbar_tools.doc
url: https://api.drupal.org/api/drupal/8
parent: admin_toolbar_tools.drupalorg
menu_name: admin
@@ -5,7 +5,9 @@
* Provides extra menu links for the core drupal toolbar.
*/
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
/**
* Implements hook_toolbar().
@@ -26,16 +28,38 @@ function admin_toolbar_tools_toolbar() {
return $items;
}
/**
* Implements hook_toolbar_alter().
*/
function admin_toolbar_tools_toolbar_alter(&$items) {
// Use lazybuilder for links so the page is cacheable.
$items['administration']['tray']['toolbar_administration'] = [
'#lazy_builder' => ['admin_toolbar_tools.toolbar_handler:lazyBuilder', []],
'#create_placeholder' => TRUE,
];
}
/**
* Implements hook_help().
*/
function admin_toolbar_tools_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.admin_toolbar_tools':
$url = Url::fromUri('https://www.drupal.org/project/admin_toolbar');
$external_link = Link::fromTextAndUrl(t('Admin Toolbar'), $url);
$external_link2 = Link::fromTextAndUrl(t('Toolbar'), $url);
$url = Url::fromUri('https://www.drupal.org/node/2713693');
$external_link3 = Link::fromTextAndUrl(t('online documentation for Admin Toolbar'), $url);
$output = '';
$output .= '<p>' . t('The Admin Toolbar Extra Tools module comes packaged with the <a href="https://www.drupal.org/project/admin_toolbar"> Admin Toolbar</a> module and adds functionality to it. The additional functionality is accessed thru extra links on the main administration <a href="https://www.drupal.org/project/toolbar">Toolbar</a>. Some links to Admin Toolbar Extra Tools administration pages are located at the bottom of this page. For more information, see the <a href="https://www.drupal.org/node/2713693">online documentation for Admin Toolbar</a>') . '</p>';
$output .= '<p>';
$output .= t('The Admin Toolbar Extra Tools module comes packaged with the :link module and adds functionality to it. The additional functionality is accessed thru extra links on the main administration :link2. Some links to Admin Toolbar Extra Tools administration pages are located at the bottom of this page. For more information, see the :link3', [
':link' => $external_link,
':link2' => $external_link2,
':link3' => $external_link3,
]);
$output .= '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<p>' . t('To use Admin Toolbar Extra Tools just install it like any other module. There is no other configuration required. The Admin Toolbar functionality can be further extended by installing complimentary modules. See <a href="https://www.drupal.org/project/admin_toolbar">Admin Toolbar</a> for a complete listing of these complimentary modules.') . '</p>';
$output .= '<p>' . t('To use Admin Toolbar Extra Tools just install it like any other module. There is no other configuration required. The Admin Toolbar functionality can be further extended by installing complimentary modules. See :link for a complete listing of these complimentary modules.', [':link' => $external_link]) . '</p>';
return $output;
}
@@ -510,6 +534,97 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
}
}
// Add menu links for local tasks that sit below any admin menu link routes.
$manager = \Drupal::service('plugin.manager.menu.local_task');
foreach ($links as $key => $link) {
// Ignore menu links that have no route name or are not in the admin menu.
// @todo Use configuration for admin menu name once added in
// https://www.drupal.org/node/1869638.
if (empty($link['route_name']) || !isset($link['menu_name']) || $link['menu_name'] !== 'admin') {
continue;
}
$route = $link['route_name'];
$route_local_tasks = $manager->getLocalTasksForRoute($route);
if (empty($route_local_tasks[0])) {
continue;
}
foreach ($route_local_tasks[0] as $plugin_id => $task_plugin) {
$definition = $task_plugin->getPluginDefinition();
$local_route = $definition['route_name'];
if (empty($local_route)) {
continue;
}
$exists = FALSE;
$param = FALSE;
if (isset($link['route_parameters'])) {
$param = $link['route_parameters'];
}
// Check if local task has an already registered link in toolbar.
foreach ($links as $link2) {
// Route_name that could be a 'class'.
if (!(isset($link2['route_name']) && ($link2['route_name'] == $local_route))) {
continue;
}
// If route are equal, the parameters could be different.
// @todo: check if this is true.
if ($param) {
if (isset($link2['route_parameters']) && ($param == $link2['route_parameters'])) {
$exists = TRUE;
break;
}
}
else {
$exists = TRUE;
break;
}
}
$parent = FALSE;
if ($exists) {
continue;
}
$title = $definition['title'];
$title = $param ? $title . ' ' . $link['title'] : $title;
// The following code is to choose the parent of the added link.
// If the parent of the current tab also has the tab as local task
// for example: "Content" has "File" as task" and and
// "Content>Comment" also has "File" as a tab then we decide to
// set this one as parent of the added link if not the current tab
// will be the parent.
if (!empty($link['parent'])) {
$parentlink = $links[$link['parent']];
$parentlink_route = $parentlink['route_name'];
$parent_primary = $manager->getLocalTasks($parentlink_route, 0);
foreach ($parent_primary['tabs'] as $parent_tab) {
if ($parent_tab['#link']['url']) {
$parent_route = $parent_tab['#link']['url']->getRouteName();
if ($parent_route == $local_route) {
$parent = $link['parent'];
break;
}
}
}
}
$parent = $parent ? $parent : $key;
// Make sure the key of the route is unique.
$links[$key . '_' . $local_route] = [
'title' => $title,
'route_name' => $local_route,
'menu_name' => 'admin',
'parent' => $parent,
'att' => TRUE,
];
if ($param) {
$links[$key . '_' . $local_route]['route_parameters'] = $param;
}
}
}
}
/**
@@ -6,38 +6,43 @@ admin_toolbar_tools.flush:
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.cssjs:
path: '/admin/flush/cssjs'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_js_css'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushJsCss'
_title: 'Flush Css and Javascript'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.plugin:
path: '/admin/flush/plugin'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_plugins'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushPlugins'
_title: 'Plugin'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.flush_static:
path: '/admin/flush/static-caches'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_static'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushStatic'
_title: 'Static caches'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.flush_menu:
path: '/admin/flush/menu'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flush_menu'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushMenu'
_title: 'Menu'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.flush_rendercache:
path: '/admin/flush/rendercache'
defaults:
@@ -46,38 +51,16 @@ admin_toolbar_tools.flush_rendercache:
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar_tools.drupalorg:
path: '/admin/drupal8'
admin_toolbar_tools.flush_views:
path: '/admin/flush/views'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::drupal_org'
_title: 'Drupal'
requirements:
_permission: 'access administration pages'
_csrf_token: 'TRUE'
admin_toolbar_tools.listchanges:
path: '/admin/drupal/list-changes'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::list_changes'
_title: 'List changes'
requirements:
_permission: 'access administration pages'
_csrf_token: 'TRUE'
admin_toolbar_tools.doc:
path: '/admin/drupal/documentation'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::documentation'
_title: 'Documentation D8'
requirements:
_permission: 'access administration pages'
_csrf_token: 'TRUE'
admin_development:
path: '/admin/development'
defaults:
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::development'
_title: 'Development'
_controller: '\Drupal\admin_toolbar_tools\Controller\ToolbarController::flushViews'
_title: 'Views'
requirements:
_permission: 'administer site configuration'
_csrf_token: 'TRUE'
admin_toolbar.run.cron:
path: '/run-cron'
defaults:
@@ -0,0 +1,4 @@
services:
admin_toolbar_tools.toolbar_handler:
class: Drupal\admin_toolbar_tools\ToolbarHandler
arguments: ['@module_handler']
@@ -2,43 +2,91 @@
namespace Drupal\admin_toolbar_tools\Controller;
use Drupal\Component\Datetime\Time;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\CronInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Component\Datetime\Time;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Menu\ContextualLinkManager;
use Drupal\Core\Menu\LocalActionManager;
use Drupal\Core\Menu\LocalTaskManager;
use Drupal\Core\Menu\MenuLinkManager;
use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Class ToolbarController
* Class ToolbarController.
*
* @package Drupal\admin_toolbar_tools\Controller
*/
class ToolbarController extends ControllerBase {
/**
* The cron service.
* A cron instance.
*
* @var $cron \Drupal\Core\CronInterface
* @var \Drupal\Core\CronInterface
*/
protected $cron;
/**
* A menu link manager instance.
*
* @var \Drupal\Core\Menu\MenuLinkManager
*/
protected $menuLinkManager;
/**
* A context link manager instance.
*
* @var \Drupal\Core\Menu\ContextualLinkManager
*/
protected $contextualLinkManager;
/**
* A local task manager instance.
*
* @var \Drupal\Core\Menu\LocalTaskManager
*/
protected $localTaskLinkManager;
/**
* A local action manager instance.
*
* @var \Drupal\Core\Menu\LocalActionManager
*/
protected $localActionLinkManager;
/**
* A cache backend interface instance.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheRender;
/**
* A date time instance.
*
* @var \Drupal\Component\Datetime\Time
*/
protected $time;
/**
* Constructs a CronController object.
* A request stack symfony instance.
*
* @param \Drupal\Core\CronInterface $cron
* The cron service.
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* A plugin cache clear instance.
*
* @var \Drupal\Core\Plugin\CachedDiscoveryClearerInterface
*/
protected $pluginCacheClearer;
/**
* {@inheritdoc}
*/
public function __construct(CronInterface $cron,
MenuLinkManager $menuLinkManager,
@@ -46,7 +94,9 @@ class ToolbarController extends ControllerBase {
LocalTaskManager $localTaskLinkManager,
LocalActionManager $localActionLinkManager,
CacheBackendInterface $cacheRender,
Time $time) {
Time $time,
RequestStack $request_stack,
CachedDiscoveryClearerInterface $plugin_cache_clearer) {
$this->cron = $cron;
$this->menuLinkManager = $menuLinkManager;
$this->contextualLinkManager = $contextualLinkManager;
@@ -54,6 +104,8 @@ class ToolbarController extends ControllerBase {
$this->localActionLinkManager = $localActionLinkManager;
$this->cacheRender = $cacheRender;
$this->time = $time;
$this->requestStack = $request_stack;
$this->pluginCacheClearer = $plugin_cache_clearer;
}
/**
@@ -67,15 +119,17 @@ class ToolbarController extends ControllerBase {
$container->get('plugin.manager.menu.local_task'),
$container->get('plugin.manager.menu.local_action'),
$container->get('cache.render'),
$container->get('datetime.time')
$container->get('datetime.time'),
$container->get('request_stack'),
$container->get('plugin.cache_clearer')
);
}
/**
* Reload the previous page.
*/
public function reload_page() {
$request = \Drupal::request();
public function reloadPage() {
$request = $this->requestStack->getCurrentRequest();
if ($request->server->get('HTTP_REFERER')) {
return $request->server->get('HTTP_REFERER');
}
@@ -90,83 +144,57 @@ class ToolbarController extends ControllerBase {
public function flushAll() {
drupal_flush_all_caches();
drupal_set_message($this->t('All caches cleared.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
/**
* Flushes css and javascript caches.
*/
public function flush_js_css() {
\Drupal::state()
public function flushJsCss() {
$this->state()
->set('system.css_js_query_string', base_convert($this->time->getCurrentTime(), 10, 36));
drupal_set_message($this->t('CSS and JavaScript cache cleared.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
/**
* Flushes plugins caches.
*/
public function flush_plugins() {
\Drupal::service('plugin.cache_clearer')->clearCachedDefinitions();
public function flushPlugins() {
$this->pluginCacheClearer->clearCachedDefinitions();
drupal_set_message($this->t('Plugins cache cleared.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
/**
* Resets all static caches.
*/
public function flush_static() {
public function flushStatic() {
drupal_static_reset();
drupal_set_message($this->t('Static cache cleared.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
/**
* Clears all cached menu data.
*/
public function flush_menu() {
public function flushMenu() {
menu_cache_clear_all();
$this->menuLinkManager->rebuild();
$this->contextualLinkManager->clearCachedDefinitions();
$this->localTaskLinkManager->clearCachedDefinitions();
$this->localActionLinkManager->clearCachedDefinitions();
drupal_set_message($this->t('Routing and links cache cleared.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
/**
* Links to drupal.org home page.
* Clears all cached views data.
*/
public function drupal_org() {
$response = new TrustedRedirectResponse("https://www.drupal.org");
$response->send();
return $response;
}
/**
* Displays the administration link Development.
*/
public function development() {
return new RedirectResponse('/admin/structure/menu/');
}
/**
* Access to Drupal 8 changes.
* (list changes of the different versions of drupal core).
*/
public function list_changes() {
$response = new TrustedRedirectResponse("https://www.drupal.org/list-changes");
$response->send();
return $response;
}
/**
* Adds a link to the Drupal 8 documentation.
*/
public function documentation() {
$response = new TrustedRedirectResponse("https://api.drupal.org/api/drupal/8");
$response->send();
return $response;
public function flushViews() {
views_invalidate_cache();
drupal_set_message($this->t('Views cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
/**
@@ -175,7 +203,7 @@ class ToolbarController extends ControllerBase {
public function runCron() {
$this->cron->run();
drupal_set_message($this->t('Cron ran successfully.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
/**
@@ -184,7 +212,7 @@ class ToolbarController extends ControllerBase {
public function cacheRender() {
$this->cacheRender->invalidateAll();
drupal_set_message($this->t('Render cache cleared.'));
return new RedirectResponse($this->reload_page());
return new RedirectResponse($this->reloadPage());
}
}
@@ -44,7 +44,7 @@ class AdminToolbarToolsAlterTest extends WebTestBase {
*/
public function testAdminToolbarTools() {
// Assert that special menu items are present in the HTML.
$this->assertRaw('class="toolbar-icon toolbar-icon-admin-toolbar-tools-flush"');
$this->assertRaw('class="toolbar-icon toolbar-icon-admin-toolbar-tools-help"');
}
}
@@ -0,0 +1,135 @@
<?php
namespace Drupal\admin_toolbar_tools;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Toolbar integration handler.
*/
class ToolbarHandler implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* The module service.
*
* @var \Drupal\Core\Extension\ModuleHandler
*/
protected $moduleHandler;
/**
* ToolbarHandler constructor.
*
* @param \Drupal\Core\Extension\ModuleHandler $module_handler
* The module service.
*/
public function __construct(ModuleHandler $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('module_handler')
);
}
/**
* Lazy builder callback for the admin_toolbar_tool items.
*
* @return array
* A renderable array as expected by the renderer service.
*/
public function lazyBuilder() {
// Render the pre_render callback we disabled earlier.
$build = admin_toolbar_prerender_toolbar_administration_tray([]);
// Add links that are uncacheable.
// Core toolbar module calculates cachability in advance so we have to build
// a fake menu tree here, including access checks.
$tools_menu = &$build['administration_menu']['#items']['admin_toolbar_tools.help']['below'];
// Adding the 'Flush all caches' menu in the correct place.
$menu_render_array = $this->createMenuRenderArray('admin_toolbar_tools.flush', $this->t('Flush all caches'), TRUE);
$this->arrayInsert($tools_menu, 1, $menu_render_array);
// Adding the submenus to 'Flush all caches' menu.
if (!empty($tools_menu['admin_toolbar_tools.flush'])) {
$tools_sub_menu = &$tools_menu['admin_toolbar_tools.flush']['below'];
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.cssjs', $this->t('Flush CSS and Javascript'));
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.plugin', $this->t('Flush plugins cache'));
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_static', $this->t('Flush static cache'));
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_menu', $this->t('Flush routing and links cache'));
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_rendercache', $this->t('Flush render cache'));
// Adding a menu link to clean the Views cache.
if ($this->moduleHandler->moduleExists('views')) {
$tools_sub_menu += $this->createMenuRenderArray('admin_toolbar_tools.flush_views', $this->t('Flush views cache'));
}
}
// Adding the 'Run Cron' menu in the correct place.
$menu_render_array = $this->createMenuRenderArray('system.run_cron', $this->t('Run cron'));
$this->arrayInsert($tools_menu, 3, $menu_render_array);
return $build;
}
/**
* Create the menu render array.
*
* @param string $route
* The route.
* @param string $title
* The menu title.
* @param bool $submenu
* Specify if the current menu element have a submenu.
*
* @return array
* A renderable array as expected by the renderer service.
*/
private function createMenuRenderArray($route, $title, $submenu = FALSE) {
$data = [];
$url = Url::fromRoute($route);
if ($url->access()) {
$data[$route] = [
'title' => $title,
'url' => $url,
'attributes' => new Attribute(['class' => ['menu-item'] + ($submenu ? ['menu-item--expanded'] : [])]),
];
if ($submenu) {
$data[$route]['below'] = [];
$data[$route]['is_expanded'] = TRUE;
}
}
return $data;
}
/**
* Insert an array in a given position of another array.
*
* @param array $array
* The array where we need to insert new elements.
* @param int $position
* The position where we will add the new array.
* @param array $insert_array
* The array that will be inserted.
*
* @see http://php.net/manual/en/function.array-splice.php#56794
*/
private function arrayInsert(array &$array, $position, array $insert_array) {
// Getting the first part of the array.
$first_array = array_splice($array, 0, $position);
// Inserting the new part in the desired position.
$array = array_merge($first_array, $insert_array, $array);
}
}
@@ -1,15 +1,18 @@
/*---------------------- menu horizontal hover---- Krout Fethi FrontEnd Developer-----*/
#toolbar-bar {
z-index: 9999;
}
.toolbar-tray-horizontal .menu-item:hover {
background: #fff;
}
.toolbar-tray-horizontal .menu-item:focus {
.toolbar-tray-horizontal .menu-item a:focus {
background: #abeae4;
}
.toolbar-tray-horizontal .toolbar-menu:not(:first-child) li.menu-item--expanded > a:focus {
background-position: center right;
background-image: url('../misc/icons/0074bd/chevron-right.svg');
background-repeat: no-repeat;
}
.toolbar-tray-horizontal .menu-item--expanded .menu {
background: #fff;
width: auto;
@@ -82,6 +85,10 @@
margin: -40px 0 0 197px;
}
.toolbar-tray-horizontal li:hover ul li {
float: none;
}
.toolbar-tray-horizontal li.hover-intent ul li {
float: none;
}
@@ -110,6 +117,10 @@
float: none !important;
}
[dir="rtl"] .toolbar-tray-horizontal li:focus ul li {
float: none !important;
}
[dir="rtl"] .toolbar .toolbar-menu ul .toolbar-icon {
padding-left: 1.3333em;
}
@@ -118,6 +129,10 @@
margin: -40px 197px 0 0;
}
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:focus ul {
margin: -40px 197px 0 0;
}
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded {
background-position: center left;
background-image: url('../misc/icons/0074bd/chevron-left.svg');
@@ -12,7 +12,39 @@
out: function () {
$(this).removeClass('hover-intent');
},
timeout: 500
timeout: 250
});
// Make the toolbar menu navigable with keyboard.
$('ul.toolbar-menu li.menu-item--expanded a').on('focusin', function () {
$('li.menu-item--expanded').removeClass('hover-intent');
$(this).parents('li.menu-item--expanded').addClass('hover-intent');
});
$('ul.toolbar-menu li.menu-item a').keydown(function (e) {
if ((e.shiftKey && (e.keyCode || e.which) == 9)) {
if ($(this).parent('.menu-item').prev().hasClass('menu-item--expanded')) {
$(this).parent('.menu-item').prev().addClass('hover-intent');
}
}
});
$('.toolbar-menu:first-child > .menu-item:not(.menu-item--expanded) a, .toolbar-tab > a').on('focusin', function () {
$('.menu-item--expanded').removeClass('hover-intent');
});
$('.toolbar-menu:first-child > .menu-item').on('hover', function () {
$(this,'a').css("background: #fff;");
});
$('ul:not(.toolbar-menu)').on({
mousemove: function () {
$('li.menu-item--expanded').removeClass('hover-intent');
},
hover: function () {
$('li.menu-item--expanded').removeClass('hover-intent');
}
});
});
})(jQuery);
@@ -4,7 +4,6 @@ namespace Drupal\admin_toolbar\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Test the existence of Admin Toolbar module.
*
@@ -43,8 +42,9 @@ class AdminToolbarAlterTest extends WebTestBase {
/**
* Tests for a the hover of sub menus.
*/
function testAdminToolbar() {
public function testAdminToolbar() {
// Assert that expanded links are present in the HTML.
$this->assertRaw('class="toolbar-icon toolbar-icon-user-admin-index"');
}
}
@@ -6,8 +6,8 @@ package: Configuration
# core: 8.x
configure: config_devel.settings
# Information added by Drupal.org packaging script on 2017-09-11
version: '8.x-1.1'
# Information added by Drupal.org packaging script on 2017-11-16
version: '8.x-1.2'
core: '8.x'
project: 'config_devel'
datestamp: 1505141047
datestamp: 1510843086
@@ -0,0 +1,13 @@
services:
config_devel.commands:
class: \Drupal\config_devel\Commands\ConfigDevelCommands
arguments:
- '@module_handler'
- '@theme_handler'
- '@info_parser'
- '@config.factory'
- '@config_devel.writeback_subscriber'
- '@config_devel.auto_import_subscriber'
- '@file_system'
tags:
- { name: drush.command }
@@ -156,15 +156,27 @@ function drush_config_devel_import_one($path) {
* The module, theme or install profile we're exporting.
* @param string $directory
* The directory we're exporting to.
*
* @return bool
* TRUE when the configuration was successfully exported. FALSE otherwise.
*/
function drush_config_devel_process_config($config_list, $type, $extension, $directory) {
$config_path = drupal_get_path($type, $extension) . "/$directory";
// Ensure the directory always exists.
if (!file_exists($config_path) && !\Drupal::service('file_system')->mkdir($config_path, NULL, TRUE)) {
drush_set_error('CONFIG_DEVEL_DIRECTORY_NOT_CREATED', sprintf('The %s directory could not be created', $config_path));
return FALSE;
}
foreach ($config_list as $name) {
$config = \Drupal::config($name);
$file_names = array($config_path . '/' . $name . '.yml');
\Drupal::service('config_devel.writeback_subscriber')->writeBackConfig($config, $file_names);
}
return TRUE;
}
/**
@@ -0,0 +1,353 @@
<?php
namespace Drupal\config_devel\Commands;
use Drupal\config_devel\EventSubscriber\ConfigDevelAutoExportSubscriber;
use Drupal\config_devel\EventSubscriber\ConfigDevelAutoImportSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Extension\InfoParserInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drush\Commands\DrushCommands;
/**
* Drush integration for the Configuration Development module.
*/
class ConfigDevelCommands extends DrushCommands {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The parser for info.yml files.
*
* @var \Drupal\Core\Extension\InfoParserInterface
*/
protected $infoParser;
/**
* The configuration object factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The event subscriber that listens to config change events.
*
* @var \Drupal\config_devel\EventSubscriber\ConfigDevelAutoExportSubscriber
*/
protected $autoExportSubscriber;
/**
* The event subscriber that listens to config change events.
*
* @var \Drupal\config_devel\EventSubscriber\ConfigDevelAutoImportSubscriber
*/
protected $autoImportSubscriber;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* Constructs a new ConfigDevelCommands object.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* The module handler.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
* The theme handler.
* @param \Drupal\Core\Extension\InfoParserInterface $infoParser
* The parser for info.yml files.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The configuration object factory.
* @param \Drupal\config_devel\EventSubscriber\ConfigDevelAutoExportSubscriber $autoExportSubscriber
* The event subscriber that listens to config change events, and happens to
* contain some code that we depend on which should be factored out into a
* separate service.
* @param \Drupal\config_devel\EventSubscriber\ConfigDevelAutoImportSubscriber $autoImportSubscriber
* The event subscriber that listens to config change events, and happens to
* contain some code that we depend on which should be factored out into a
* separate service.
* @param \Drupal\Core\File\FileSystemInterface $fileSystem
* The file system service.
*/
public function __construct(ModuleHandlerInterface $moduleHandler, ThemeHandlerInterface $themeHandler, InfoParserInterface $infoParser, ConfigFactoryInterface $configFactory, ConfigDevelAutoExportSubscriber $autoExportSubscriber, ConfigDevelAutoImportSubscriber $autoImportSubscriber, FileSystemInterface $fileSystem) {
parent::__construct();
$this->moduleHandler = $moduleHandler;
$this->themeHandler = $themeHandler;
$this->infoParser = $infoParser;
$this->configFactory = $configFactory;
// @todo We should not depend on event subscribers directly.
// @see https://www.drupal.org/node/2388253
$this->autoExportSubscriber = $autoExportSubscriber;
$this->autoImportSubscriber = $autoImportSubscriber;
$this->fileSystem = $fileSystem;
}
/**
* Write back configuration to module's config directory.
*
* List which configuration settings you want to export in the module's info
* file by listing them under 'config_devel', as shown below:
*
* config_devel:
* install:
* - entity.view_display.node.article.default
* - entity.view_display.node.article.teaser
* - field.instance.node.article.body
* optional:
* - field.instance.node.article.tags
*
* @command config-devel-export
* @param string $extension Machine name of the module, profile or theme to export.
* @usage drush config-devel-export MODULE_NAME
* Write back configuration to the specified module, based on .info file.
* @aliases cde,cd-em
*
* @throws \Exception
* Thrown when the passed in extension
*/
public function export($extension) {
// Determine the type of extension we're dealing with.
$type = $this->getExtensionType($extension);
if (!$type) {
throw new \Exception("Couldn't export configuration. The '$extension' extension is not enabled.");
}
// Get the config.
$config = $this->getExtensionConfig($type, $extension);
// Export the required config.
if (isset($config['install'])) {
$this->exportConfig($config['install'], $type, $extension, InstallStorage::CONFIG_INSTALL_DIRECTORY);
}
// If we have any optional configuration, export that as well.
if (isset($config['optional'])) {
$this->exportConfig($config['optional'], $type, $extension, InstallStorage::CONFIG_OPTIONAL_DIRECTORY);
}
}
/**
* Import configuration from module's config directory to active storage.
*
* List which configuration settings you want to export in the module's info
* file by listing them under 'config_devel', as shown below:
*
* config_devel:
* install:
* - entity.view_display.node.article.default
* - entity.view_display.node.article.teaser
* - field.instance.node.article.body
* optional:
* - field.instance.node.article.tags
*
* @command config-devel-import
* @param string $extension Machine name of the module, profile or theme.
* @usage drush config-devel-import MODULE_NAME
* Import configuration from the specified module, profile or theme into the active storage, based on .info file.
* @aliases cdi,cd-im
*
* @throws \Exception
* Thrown when the passed in extension is not enabled.
*/
public function import($extension) {
// Determine the type of extension we're dealing with.
$type = $this->getExtensionType($extension);
if (!$type) {
throw new \Exception("Couldn't import configuration. The '$extension' extension is not enabled.");
}
// Get the config
$config = $this->getExtensionConfig($type, $extension);
// Import config
if (isset($config['install'])) {
$this->importConfig($config['install'], $type, $extension, InstallStorage::CONFIG_INSTALL_DIRECTORY);
}
// Import optional config
if (isset($config['optional'])) {
$this->importConfig($config['optional'], $type, $extension, InstallStorage::CONFIG_OPTIONAL_DIRECTORY);
}
}
/**
* Import a single config item into active storage.
*
* List which configuration settings you want to export in the module's info
* file by listing them under 'config_devel', as shown below:
*
* config_devel:
* install:
* - entity.view_display.node.article.default
* - entity.view_display.node.article.teaser
* - field.instance.node.article.body
* optional:
* - field.instance.node.article.tags
*
* @command config-devel-import-one
* @param string $path Config file name.
* @usage drush config-devel-import-one system.site.yml
* Import the contents of system.site.yml into the config object system.site.
* @usage drush config-devel-import-one system.site
* Import the standard input into the config object system.site. Helpful for scripting copying to remote.
* @aliases cdi1,cd-i1
*
* @throws \Exception
* Thrown when the given file was not found.
*/
public function importSingle($path) {
$contents = '';
if (!file_exists($path)) {
if (substr($path, -4) != '.yml') {
$contents = file_get_contents('php://stdin');
}
elseif (!empty($_SERVER['PWD'])) {
$path = $_SERVER['PWD'] . '/' . trim($path, '/');
}
}
if ($contents || file_exists($path)) {
$this->autoImportSubscriber->importOne($path, '', $contents);
}
else {
throw new \Exception("File '$path' not found.");
}
}
/**
* Returns the type for the given extension.
*
* @param string $extension
* The extension name.
*
* @return string
* Either 'module', 'theme', 'profile', or NULL if no valid extension was
* provided.
*/
protected function getExtensionType($extension) {
$type = NULL;
if ($this->moduleHandler->moduleExists($extension)) {
$type = 'module';
}
elseif ($this->themeHandler->themeExists($extension)) {
$type = 'theme';
}
elseif (\Drupal::installProfile() === $extension) {
$type = 'profile';
}
return $type;
}
/**
* Returns the config for the given extension.
*
* @param string $type
* Either 'module', 'theme' or 'profile'.
* @param string $extension
* The name of the extension for which to return the config.
*
* @return array
* An array containing install and optional config.
*/
protected function getExtensionConfig($type, $extension) {
$filename = drupal_get_path($type, $extension) . '/' . $extension .'.info.yml';
$info = $this->infoParser->parse($filename);
$config = [];
if (isset($info['config_devel'])) {
// Keep backwards compatibility for the old format. This has config names
// listed directly beneath 'config_devel', rather than an intermediate
// level for 'install' and 'optional'.
// Detect the old format based on whether there's neither of these two
// keys.
if (!isset($info['config_devel']['install']) && !isset($info['config_devel']['optional'])) {
$info['config_devel']['install'] = $info['config_devel'];
}
$config['install'] = $info['config_devel']['install'];
// If we have any optional configuration, fetch that as well.
if (isset($info['config_devel']['optional'])) {
$config['optional'] = $info['config_devel']['optional'];
}
}
return $config;
}
/**
* Exports a list of configuration entities.
*
* @param array $config_list
* An array of configuration entities.
* @param string $type
* The type of extension we're exporting, one of 'module', 'profile' or
* 'theme'.
* @param string $extension
* The name of the extension we're exporting.
* @param string $directory
* The directory we're exporting to.
*
* @throws \Exception
* Thrown when the directory to export to is missing and could not be
* created.
*/
protected function exportConfig($config_list, $type, $extension, $directory) {
$config_path = drupal_get_path($type, $extension) . "/$directory";
// Ensure the directory always exists.
if (!file_exists($config_path) && !$this->fileSystem->mkdir($config_path, NULL, TRUE)) {
throw new \Exception(sprintf('The %s directory could not be created', $config_path));
}
foreach ($config_list as $name) {
$config = $this->configFactory->get($name);
$file_names = [$config_path . '/' . $name . '.yml'];
$this->autoExportSubscriber->writeBackConfig($config, $file_names);
}
}
/**
* Imports a list of configuration entities.
*
* @param array $config_list
* An array of configuration entities.
* @param string $type
* The type of extension we're importing, one of 'module'. 'profile' or
* 'theme'.
* @param string $extension
* The module, theme or install profile we're importing.
* @param string $directory
* The directory we're importing.
*/
protected function importConfig($config_list, $type, $extension, $directory) {
$config_path = drupal_get_path($type, $extension) . "/$directory";
foreach ($config_list as $name) {
$file_name = $config_path . '/' . $name . '.yml';
$this->importSingle($file_name);
}
}
}
@@ -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,77 @@
The migrate_plus module extends the core migration system with API enhancements
and additional functionality, as well as providing practical examples.
Extensions to base API
======================
* A Migration configuration entity is provided, enabling persistance of dynamic
migration configuration.
* A MigrationGroup configuration entity is provided, which enables migrations to
be organized in groups, and to maintain shared configuration in one place.
* A MigrateEvents::PREPARE_ROW event is provided to dispatch hook_prepare_row()
invocations as events.
* A SourcePluginExtension class is provided, enabling one to define fields and
IDs for a source plugin via configuration rather than requiring PHP code.
Plugin types
============
migrate_plus provides the following plugin types, for use with the url source
plugin.
* A data_parser type, for parsing different formats on behalf of the url source
plugin.
* A data_fetcher type, for fetching data to feed into a data_parser plugin.
* An authentication type, for adding authentication headers with the http
data_fetcher plugin.
Plugins
=======
Process
-------
* The entity_lookup process plugin allows you to populate references to entities
which already exist in Drupal, whether they were migrated or not.
* The entity_generate process plugin extends entity_lookup to also create the
desired entity when it doesn't already exist.
* The file_blob process plugin supports creating file entities from blob data.
* The merge process plugin allows the merging of multiple arrays into a single
field.
* The skip_on_value process plugin allows you to skip a row, or a given field,
for specific source values.
Source
------
* A url source plugin is provided, implementing a common structure for
file-based data providers.
Data parsers
------------
* The xml parser plugin uses PHP's XMLReader interface to incrementally parse
XML files. This should be used for XML sources which are potentially very
large.
* The simple_xml parser plugin uses PHP's SimpleXML interface to fully parse
XML files. This should be used for XML sources where you need to be able to
use complex xpaths for your item selectors, or have to access elements outside
of the current item element via xpaths.
* The json parser plugin supports JSON sources.
* The soap parser plugin supports SOAP sources.
Data fetchers
-------------
* The file fetcher plugin works for most URLs regardless of protocol, as well as
local filesystems.
* The http fetcher plugin provides the ability to add headers to an HTTP
request (particularly through authentication plugins).
Authentication
--------------
* The basic authentication plugin provides HTTP Basic authentication.
* The digest authentication plugin provides HTTP Digest authentication.
* The oauth2 authentication plugin provides OAuth2 authentication over HTTP.
Examples
========
* The migrate_example submodule provides a fully functional and runnable
example migration scenario demonstrating the basic concepts and most common
techniques for SQL-based migrations.
* The migrate_example_advanced submodule provides examples of migration from
different kinds of sources, as well as less common techniques.
@@ -0,0 +1,24 @@
{
"name": "drupal/migrate_plus",
"description": "Enhancements to core migration support.",
"type": "drupal-module",
"license": "GPL-2.0+",
"homepage": "https://www.drupal.org/project/migrate_plus",
"authors": [
{
"name": "Mike Ryan",
"homepage":"https://www.drupal.org/u/mikeryan",
"role": "Maintainer"
}
],
"support": {
"issues": "https://www.drupal.org/project/issues/migrate_plus",
"irc": "irc://irc.freenode.org/drupal-migrate",
"source": "https://cgit.drupalcode.org/migrate_plus"
},
"minimum-stability": "dev",
"require": {},
"suggest": {
"sainsburys/guzzle-oauth2-plugin": "3.0 required for the OAuth2 authentication plugin"
}
}
@@ -0,0 +1,40 @@
# Basic data types for Migrate.
migrate_plugin:
type: mapping
mapping:
plugin:
type: string
label: 'Plugin'
migrate_destination:
type: migrate_plugin
label: 'Destination'
mapping:
overwrite_properties:
type: sequence
label: 'Properties to overwrite'
sequence:
type: string
label: 'Property'
migrate_source:
type: migrate_plugin
label: 'Source'
mapping:
constants:
type: ignore
label: 'Constants'
migrate_process:
type: migrate_plugin
label: 'Process'
# Base schema for migrate source plugins that extend
# \Drupal\migrate\Plugin\migrate\source\SqlBase.
migrate_source_sql:
type: migrate_source
mapping:
target:
type: string
label: 'The migration database target'
@@ -0,0 +1,18 @@
# Schema for the migrate destination plugins.
migrate_plus.destination.*:
type: migrate_destination
label: 'Default destination'
mapping:
no_stub:
type: boolean
label: 'Whether stubbing is allowed.'
default: false
migrate_plus.destination.config:
type: migrate_destination
label: 'Config'
mapping:
config_name:
type: string
label: 'Configuration name'
@@ -0,0 +1,145 @@
# Schema for the migrate process plugins.
migrate_plus.process.*:
type: migrate_process
label: 'Default process'
migrate_plus.process.callback:
type: migrate_process
label: 'Callback process'
mapping:
callback:
type: string
label: 'Callback'
migrate_plus.process.concat:
type: migrate_process
label: 'Concat process'
mapping:
delimiter:
type: string
label: 'Delimiter'
migrate_plus.process.dedupe_entity:
type: migrate_process
label: 'Dedupe Entity process'
mapping:
entity_type:
type: string
label: 'Entity type'
field:
type: string
label: 'Field name'
postfix:
type: string
label: 'Postfix'
start:
type: integer
label: 'Start'
length:
type: integer
label: 'Length'
migrate_plus.process.explode:
type: migrate_process
label: 'Explode process'
mapping:
delimiter:
type: string
label: 'Delimiter'
limit:
type: integer
label: 'Limit'
migrate_plus.process.extract:
type: migrate_process
label: 'Extract process'
mapping:
default:
type: string
label: 'Default value'
migrate_plus.process.flatten:
type: migrate_process
label: 'Flatten process'
migrate_plus.process.get:
type: migrate_process
label: 'Get process'
mapping:
source:
type: string
label: 'Source key'
migrate_plus.process.iterator:
type: migrate_process
label: 'Iterator process'
mapping:
process:
type: ignore
label: 'Process'
key:
type: string
label: 'Key'
migrate_plus.process.machine_name:
type: migrate_process
label: 'Machine name process'
migrate_plus.process.migration:
type: migrate_process
label: 'Migration process'
mapping:
migration:
type: sequence
label: 'Migration'
source:
type: sequence
label: 'Source keys'
source_ids:
type: string
label: 'Source IDs'
stub_id:
type: string
label: 'Stub ID'
migrate_plus.process.route:
type: migrate_process
label: 'Route process'
migrate_plus.process.skip_on_empty:
type: migrate_process
label: 'Skip on Empty'
migrate_plus.process.skip_row_if_not_set:
type: migrate_process
label: 'Skip Row process if not set'
mapping:
index:
type: integer
label: 'Index'
migrate_plus.process.static_map:
type: migrate_process
label: 'Static Map'
mapping:
map:
type: sequence
label: 'Map'
default_value:
type: string
label: 'Default value'
bypass:
type: boolean
label: 'Bypass lookup'
migrate_plus.process.default_value:
type: migrate_process
label: 'Default value'
mapping:
strict:
type: boolean
label: 'Strict type check'
default_value:
type: string
label: 'Default value'
@@ -0,0 +1,78 @@
# Schema for the configuration files of the Migrate Plus module.
migrate_plus.migration.*:
type: config_entity
label: 'Migration'
mapping:
id:
type: string
label: 'ID'
class:
type: string
label: 'Class'
field_plugin_method:
type: string
label: 'Field Plugin Method'
cck_plugin_method:
type: string
label: 'BC layer for Field Plugin Method'
migration_tags:
type: sequence
label: 'Migration Tags'
sequence:
type: string
label: 'Tag'
migration_group:
type: string
label: 'Group'
label:
type: label
label: 'Label'
source:
type: migrate_plus.source.[plugin]
label: 'Source'
process:
type: ignore
label: 'Process'
destination:
type: migrate_plus.destination.[plugin]
label: 'Destination'
migration_dependencies:
type: mapping
label: 'Dependencies'
mapping:
required:
type: sequence
label: 'Required dependencies'
sequence:
type: string
label: 'Dependency'
optional:
type: sequence
label: 'Optional dependencies'
sequence:
type: string
label: 'Dependency'
migrate_plus.migration_group.*:
type: config_entity
label: 'Migration Group'
mapping:
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
description:
type: string
label: 'Description'
source_type:
type: string
label: 'Source type'
module:
type: string
label: 'Dependent module'
shared_configuration:
type: ignore
label: 'Shared migration configuration'
@@ -0,0 +1,34 @@
# Schema for the migrate source plugins.
migrate_plus.source.*:
type: migrate_source
label: 'Default source'
migrate_plus.source.empty:
type: migrate_source_sql
label: 'Empty source'
mapping:
provider:
type: string
label: 'Provider'
migrate_plus.source.embedded_data:
type: migrate_source
label: 'Embedded data source'
mapping:
data_rows:
type: sequence
label: 'Data rows'
sequence:
type: ignore
label: 'Data row'
ids:
type: sequence
label: 'Unique key'
sequence:
type: mapping
label: 'Key column'
mapping:
type:
type: string
label: 'Column type'
@@ -0,0 +1,111 @@
INTRODUCTION
------------
The migrate_example module demonstrates how to implement custom migrations
for Drupal 8. It includes a group of "beer" migrations demonstrating a complete
simple migration scenario.
THE BEER SITE
-------------
In this scenario, we have a beer aficionado site which stores its data in MySQL
tables - there are content items for each beer on the site, user accounts with
profile data, categories to classify the beers, and user-generated comments on
the beers. We want to convert this site to Drupal with just a few modifications
to the basic structure.
To make the example as simple as to run as possible, the source data is placed
in tables directly in your Drupal database - in most real-world scenarios, your
source data will be in an external database. The migrate_example_setup submodule
creates and populates these tables, as well as configuring your Drupal 8 site
(creating a node type, vocabulary, fields, etc.) to receive the data.
STRUCTURE
---------
There are two primary components to this example:
1. Migration configuration, in the migrations and config/install directories.
These YAML files describe the migration process and provide the mappings from
the source data to Drupal's destination entities. The difference between the
two possible directories:
a. Files in the migrations directory provide configuration directly for the
migration plugins. The filenames are of the form <migration ID>.yml. This
approach is recommended when your migration configuration is fully hardcoded
and does not need to be overridden (e.g., you don't need to change the URL to
a source web service through an admin UI). While developing migrations,
changes to these files require at most a 'drush cr' to load your changes.
b. Files in the config/install directory provide migration configuration as
configuration entities, and have names of the form
migrate_plus.migration.<migration ID>.yml ("migration" because they define
entities of the "migration" type, and "migrate_plus" because that is the
module which implements the "migration" type). Migrations defined in this way
may have their configuration modified (in particular, through a web UI) by
loading the configuration entity, modifying its configuration, and saving the
entity. When developing, to get edits to the .yml files in config/install to
take effect in active configuration, use the config_devel module.
Configuration in either type of file is identical - the only differences are
the directories and filenames.
2. Source plugins, in src/Plugin/migrate/source. These are referenced from the
configuration files, and provide the source data to the migration processing
pipeline, as well as manipulating that data where necessary to put it into
a canonical form for migrations.
UNDERSTANDING THE MIGRATIONS
----------------------------
The YAML and PHP files are copiously documented in-line. To best understand
the concepts described in a more-or-less narrative form, it is recommended you
read the files in the following order:
1. migrate_plus.migration_group.beer.yml
2. migrate_plus.migration.beer_term.yml
3. BeerTerm.php
4. migrate_plus.migration.beer_user.yml
5. BeerUser.php
6. migrate_plus.migration.beer_node.yml
7. BeerNode.php
8. migrate_plus.migration.beer_comment.yml
9. BeerComment.php
RUNNING THE MIGRATIONS
----------------------
The migrate_tools module (https://www.drupal.org/project/migrate_tools) provides
the tools you need to perform migration processes. At this time, the web UI only
provides status information - to perform migration operations, you need to use
the drush commands.
# Enable the tools and the example module if you haven't already.
drush en -y migrate_tools,migrate_example
# Look at the migrations. Just look at them. Notice that they are displayed in
# the order they will be run, which reflects their dependencies. For example,
# because the node migration references the imported terms and users, it must
# run after those migrations have been run.
drush ms # Abbreviation for migrate-status
# Run the import operation for all the beer migrations.
drush mi --group=beer # Abbreviation for migrate-import
# Look at what you've done! Also, visit the site and see the imported content,
# user accounts, etc.
drush ms
# Look at the duplicate username message.
drush mmsg beer_user # Abbreviation for migrate-messages
# Run the rollback operation for all the migrations (removing all the imported
# content, user accounts, etc.). Note that it will rollback the migrations in
# the opposite order as they were imported.
drush mr --group=beer # Abbreviation for migrate-rollback
# You can import specific migrations.
drush mi beer_term,beer_user
# At this point, go look at your content listing - you'll see beer nodes named
# "Stub", generated from the user's favbeers references.
drush mi beer_node,beer_comment
# Refresh your content listing - the stub nodes have been filled with real beer!
# You can rollback specific migrations.
drush mr beer_comment,beer_node
@@ -0,0 +1,53 @@
# Migration configuration for beer content.
id: beer_node
label: Beers of the world
migration_group: beer
source:
plugin: beer_node
destination:
plugin: entity:node
process:
# Hardcode the destination node type (bundle) as 'migrate_example_beer'.
type:
plugin: default_value
default_value: migrate_example_beer
title: name
nid: bid
uid:
plugin: migration
migration: beer_user
source: aid
sticky:
plugin: default_value
default_value: 0
field_migrate_example_country: countries
field_migrate_example_beer_style:
plugin: migration
migration: beer_term
source: terms
# Some Drupal fields may have multiple components we may want to set
# separately. For example, text fields may have summaries (teasers) in
# addition to the full text value. We use / to separate the field name from
# the internal field value being set, and put it in quotes because / is a
# YAML special character.
'body/value': body
'body/summary': excerpt
# Our beer nodes have references to terms and users, so we want those to be
# imported first. We make that dependency explicit here - by putting those
# migrations under the 'required' key, we ensure that the tools will prevent
# us from running the beer_node migration unless the beer_term and beer_user
# migrations are complete (although we can override the dependency check by
# passing --force to the drush migrate-import command). We can also add
# 'optional' dependencies - these affect the order in which migrations are
# displayed, and run by default, but does not force you run them in that
# order.
# The general rule of thumb is that any migrations referenced by migration
# process plugins should be required here.
migration_dependencies:
required:
- beer_term
- beer_user
dependencies:
enforced:
module:
- migrate_example
@@ -0,0 +1,87 @@
# A "migration" is, in technical terms, a plugin whose configuration describes
# how to read source data, process it (generally by mapping source fields to
# destination fields), and write it to Drupal.
# The machine name for a migration, used to uniquely identify it.
id: beer_term
# A human-friendly description of the migration.
label: Migrate style categories from the source database to taxonomy terms
# The machine name of the group containing this migration (which contains shared
# configuration to be merged with our own configuration here).
migration_group: beer
# Every migration must have a source plugin, which controls the delivery of our
# source data. In this case, our source plugin has the name "beer_term", which
# Drupal resolves to the PHP class defined in
# src/Plugin/migrate/source/BeerTerm.php.
source:
plugin: beer_term
# Every migration must also have a destination plugin, which handles writing
# the migrated data in the appropriate form for that particular kind of data.
# Most Drupal content is an "entity" of one type or another, and we need to
# specify what entity type we are populating (in this case, taxonomy terms).
# Unlike the source plugin (which is specific to our particular scenario), this
# destination plugin is implemented in Drupal itself.
destination:
plugin: entity:taxonomy_term
# Here's the meat of the migration - the processing pipeline. This describes how
# each destination field is to be populated based on the source data. For each
# destination field, one or more process plugins may be invoked.
process:
# The simplest process plugin is named 'get' - it is the default plugin, so
# does not need to be explicitly named. It simply copies the source value
# (the 'style' field from the source database in this case) to the destination
# field (the taxonomy term 'name' field). You can see we simply copy the
# source 'details' field to destination 'description' field in the same way.
name: style
description: details
# Here is a new plugin - default_value. In its simplest usage here, it is used
# to hard-code a destination value, the vid (vocabulary ID) our taxonomy terms
# should be assigned to. It's important to note that while above the right
# side of the mappings was a source field name, here the right side of the
# 'default_value:' line is an actual value.
vid:
plugin: default_value
default_value: migrate_example_beer_styles
# Here's another new plugin - migration. When importing data from another
# system, typically the unique identifiers for items on the destination side
# are not the same as the identifiers were on the source side. For example, in
# our style data the term names are the unique identifiers for each term,
# while in Drupal each term is assigned a unique integer term ID (tid). When
# any such items are referenced in Drupal, the reference needs to be
# translated from the old ID ('ale') to the new ID (1). The migration
# framework keeps track of the relationships between source and destination
# IDs in map tables, and the migration plugin is the means of performing a
# lookup in those map tables during processing.
parent:
plugin: migration
# Here we reference the migration whose map table we're performing a lookup
# against. You'll note that in this case we're actually referencing this
# migration itself, since category parents are imported by the same
# migration. This works best when we're sure the parents are imported
# before the children, and in this case our source plugin is guaranteeing
# that.
migration: beer_term
# 'style_parent' is the parent reference field from the source data. The
# result of this plugin is that the destination 'parent' field is populated
# with the Drupal term ID of the referenced style (or NULL if style_parent
# was empty).
source: style_parent
# We'll learn more about dependencies in beer_node - here, we leave them empty.
migration_dependencies: {}
# By default, configuration entities (like this migration) are not automatically
# removed when the migration which installed them is uninstalled. To have your
# migrations uninstalled with your migration module, add an enforced dependency
# on your module.
dependencies:
enforced:
module:
- migrate_example
@@ -0,0 +1,105 @@
# Migration configuration for user accounts. We've described most of what goes
# into migration configuration in migrate_plus.migration.beer_term.yml, so won't
# repeat that here.
id: beer_user
label: Beer Drinkers of the world
migration_group: beer
source:
plugin: beer_user
destination:
plugin: entity:user
process:
pass: password
mail: email
init: email
status: status
roles:
plugin: default_value
default_value: 2
# Here's a new process plugin - dedupe_entity. Our source site allowed there
# to be multiple user accounts with the same username, but Drupal wants
# usernames to be unique. This plugin allows us to automatically generate
# unique usernames when we detect collisions.
name:
plugin: dedupe_entity
# The name of the source field containing the username.
source: username
# These next two settings identify the destination-side field to check for
# duplicates. They say "see if the incoming 'name' matches any existing
# 'name' field in any 'user' entity".
entity_type: user
field: name
# Finally, this specifies a string to use between the original value and the
# sequence number appended to make the value unique. Thus, the first 'alice'
# account gets the name 'alice' in Drupal, and the second one gets the name
# 'alice_1'.
postfix: _
# Another new process plugin - callback. This allows us to filter an incoming
# source value through an arbitrary PHP function. The function called must
# have one required argument.
created:
plugin: callback
# The 'registered' timestamp in the source data is a string of the form
# 'yyyy-mm-dd hh:mm:ss', but Drupal wants a UNIX timestamp for 'created'.
source: registered
callable: strtotime
# Our source data only has a single timestamp value, 'registered', which we
# want to use for all four of Drupal's user timestamp fields. We could
# duplicate the callback plugin we used for 'created' above - but we have a
# shortcut. Putting an @ sign at the beginning of the source value indicates
# that it is to be interpreted as a *destination* field name instead of a
# *source* field name. Thus, if a value we need in more than one place
# requires some processing beyond simply copying it directly, we can perform
# that processing a single time and use the result in multiple places.
changed: '@created'
access: '@created'
login: '@created'
# Yet another new process plugin - static_map. We're making a transformation
# in how we represent gender data - formerly it was integer values 0 for male
# and 1 for female, but in our modern Drupal site we will be making this a
# free-form text field, so we want to replace the obscure integers with
# simple strings.
field_migrate_example_gender:
plugin: static_map
# Specify the source field we're reading (containing 0's and 1's).
source: sex
# Tell it to transform 0 to 'Male', and 1 to 'Female'.
map:
0: Male
1: Female
# If the input is missing, leave the field empty. Without this, an empty
# or invalid source value would cause the user record to be skipped
# entirely.
bypass: true
# This looks like a simple migration process plugin, but there's magic
# happening here. We import nodes after terms and users, because they have
# references to terms and users, so of course the terms and users must be
# migrated first - right? However, the favbeers field is a reference to the
# beer nodes which haven't yet been migrated - we have a circular relationship
# between users and nodes. The way the migration system resolves this
# situation is by creating "stubs". In this case, because no beer nodes have
# been created, each time a beer is looked up against the beer_node migration
# nothing is found, and by default the migration process plugin creates an
# empty stub node as a placeholder so the favbeers reference field has
# something to point to. The stub is recorded in the beer_node map table, so
# when that migration runs it knows that each incoming beer should overwrite
# its stub instead of creating a new node.
field_migrate_example_favbeers:
plugin: migration
source: beers
migration: beer_node
migration_dependencies: {}
# When a module is creating a custom content type it needs to add an
# enforced dependency to itself, otherwise the content type will persist
# after the module is disabled. See: https://www.drupal.org/node/2629516.
dependencies:
enforced:
module:
- migrate_example
@@ -0,0 +1,38 @@
# A "migration group" is - surprise! - a group of migrations. It is used to
# group migrations for display by our tools, and to perform operations on a
# specific set of migrations. It can also be used to hold any configuration
# common to those migrations, so it doesn't have to be duplicated in each one.
# The machine name of the group, by which it is referenced in individual
# migrations.
id: beer
# A human-friendly label for the group.
label: Beer Imports
# More information about the group.
description: A few simple beer-related imports, to demonstrate how to implement migrations.
# Short description of the type of source, e.g. "Drupal 6" or "WordPress".
source_type: Custom tables
# Here we add any default configuration settings to be shared among all
# migrations in the group. For this example, the source tables are in the
# Drupal (default) database, but usually if your source data is in a
# database it will be external.
shared_configuration:
# Specifying 'source' here means that this configuration will be merged into
# the 'source' configuration of each migration.
source:
# A better practice for real-world migrations would be to add a database
# connection to your external database in settings.php and reference its
# key here.
key: default
# As with the migration configuration (see beer_term), we add an enforced
# dependency so the migration_group configuration will be removed on module
# uninstall.
dependencies:
enforced:
module:
- migrate_example
@@ -0,0 +1,15 @@
type: module
name: Migrate Example
description: 'Examples of how Drupal 8 migration compares to previous versions.'
package: Examples
# core: 8.x
dependencies:
- drupal:migrate
- migrate_plus:migrate_example_setup
- migrate_plus:migrate_plus
# Information added by Drupal.org packaging script on 2017-05-10
version: '8.x-4.0-beta1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1494450189
@@ -0,0 +1,7 @@
langcode: en
status: true
dependencies: { }
id: node_comments
label: 'Node comments'
target_entity_type_id: node
description: ''
@@ -0,0 +1,30 @@
langcode: en
status: true
dependencies:
config:
- comment.type.node_comments
- field.field.comment.node_comments.comment_body
module:
- text
id: comment.node_comments.default
targetEntityType: comment
bundle: node_comments
mode: default
content:
author:
weight: -2
comment_body:
type: text_textarea
weight: 11
settings:
rows: 5
placeholder: ''
third_party_settings: { }
subject:
type: string_textfield
weight: 10
settings:
size: 60
placeholder: ''
third_party_settings: { }
hidden: { }
@@ -0,0 +1,87 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.migrate_example_beer.body
- field.field.node.migrate_example_beer.field_comments
- field.field.node.migrate_example_beer.field_migrate_example_beer_style
- field.field.node.migrate_example_beer.field_migrate_example_country
- field.field.node.migrate_example_beer.field_migrate_example_image
- node.type.migrate_example_beer
module:
- comment
- image
- text
id: node.migrate_example_beer.default
targetEntityType: node
bundle: migrate_example_beer
mode: default
content:
body:
type: text_textarea_with_summary
weight: 6
settings:
rows: 9
summary_rows: 3
placeholder: ''
third_party_settings: { }
created:
type: datetime_timestamp
weight: 2
settings: { }
third_party_settings: { }
field_comments:
weight: 10
settings: { }
third_party_settings: { }
type: comment_default
field_migrate_example_beer_style:
weight: 7
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
type: entity_reference_autocomplete
field_migrate_example_country:
weight: 8
settings:
size: 60
placeholder: ''
third_party_settings: { }
type: string_textfield
field_migrate_example_image:
weight: 9
settings:
progress_indicator: throbber
preview_image_style: thumbnail
third_party_settings: { }
type: image_image
promote:
type: boolean_checkbox
settings:
display_label: true
weight: 3
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 4
third_party_settings: { }
title:
type: string_textfield
weight: 0
settings:
size: 60
placeholder: ''
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 1
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
hidden: { }
@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
config:
- comment.type.node_comments
- field.field.comment.node_comments.comment_body
module:
- text
id: comment.node_comments.default
targetEntityType: comment
bundle: node_comments
mode: default
content:
comment_body:
label: hidden
type: text_default
weight: 0
settings: { }
third_party_settings: { }
links:
weight: 100
hidden: { }
@@ -0,0 +1,60 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.migrate_example_beer.body
- field.field.node.migrate_example_beer.field_comments
- field.field.node.migrate_example_beer.field_migrate_example_beer_style
- field.field.node.migrate_example_beer.field_migrate_example_country
- field.field.node.migrate_example_beer.field_migrate_example_image
- node.type.migrate_example_beer
module:
- comment
- image
- text
- user
id: node.migrate_example_beer.default
targetEntityType: node
bundle: migrate_example_beer
mode: default
content:
body:
label: hidden
type: text_default
weight: 2
settings: { }
third_party_settings: { }
field_comments:
weight: 5
label: above
settings:
pager_id: 0
third_party_settings: { }
type: comment_default
field_migrate_example_beer_style:
weight: 3
label: above
settings:
link: true
third_party_settings: { }
type: entity_reference_label
field_migrate_example_country:
weight: 4
label: above
settings:
link_to_entity: false
third_party_settings: { }
type: string
field_migrate_example_image:
weight: 1
label: above
settings:
image_style: ''
image_link: ''
third_party_settings: { }
type: image
links:
weight: 0
settings: { }
third_party_settings: { }
hidden: { }
@@ -0,0 +1,35 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
- field.field.node.migrate_example_beer.body
- field.field.node.migrate_example_beer.field_comments
- field.field.node.migrate_example_beer.field_migrate_example_beer_style
- field.field.node.migrate_example_beer.field_migrate_example_country
- field.field.node.migrate_example_beer.field_migrate_example_image
- node.type.migrate_example_beer
module:
- text
- user
id: node.migrate_example_beer.teaser
targetEntityType: node
bundle: migrate_example_beer
mode: teaser
content:
body:
label: hidden
type: text_summary_or_trimmed
weight: 1
settings:
trim_length: 600
third_party_settings: { }
links:
weight: 0
settings: { }
third_party_settings: { }
hidden:
field_comments: true
field_migrate_example_beer_style: true
field_migrate_example_country: true
field_migrate_example_image: true
@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
config:
- comment.type.node_comments
- field.storage.comment.comment_body
module:
- text
id: comment.node_comments.comment_body
field_name: comment_body
entity_type: comment
bundle: node_comments
label: Comment
description: ''
required: true
translatable: true
default_value: { }
default_value_callback: ''
settings: { }
field_type: text_long
@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.body
- node.type.migrate_example_beer
module:
- text
id: node.migrate_example_beer.body
field_name: body
entity_type: node
bundle: migrate_example_beer
label: Body
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
display_summary: true
field_type: text_with_summary
@@ -0,0 +1,32 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_comments
- node.type.migrate_example_beer
module:
- comment
id: node.migrate_example_beer.field_comments
field_name: field_comments
entity_type: node
bundle: migrate_example_beer
label: Comments
description: ''
required: false
translatable: false
default_value:
-
status: 2
cid: 0
last_comment_timestamp: 0
last_comment_name: null
last_comment_uid: 0
comment_count: 0
default_value_callback: ''
settings:
default_mode: 1
per_page: 50
anonymous: 0
form_location: true
preview: 1
field_type: comment
@@ -0,0 +1,26 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_beer_style
- node.type.migrate_example_beer
- taxonomy.vocabulary.migrate_example_beer_styles
id: node.migrate_example_beer.field_migrate_example_beer_style
field_name: field_migrate_example_beer_style
entity_type: node
bundle: migrate_example_beer
label: 'Migrate Example Beer Styles'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:taxonomy_term'
handler_settings:
target_bundles:
migrate_example_beer_styles: migrate_example_beer_styles
sort:
field: _none
auto_create: false
field_type: entity_reference
@@ -0,0 +1,18 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_country
- node.type.migrate_example_beer
id: node.migrate_example_beer.field_migrate_example_country
field_name: field_migrate_example_country
entity_type: node
bundle: migrate_example_beer
label: Countries
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string
@@ -0,0 +1,37 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_image
- node.type.migrate_example_beer
module:
- image
id: node.migrate_example_beer.field_migrate_example_image
field_name: field_migrate_example_image
entity_type: node
bundle: migrate_example_beer
label: Image
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
file_directory: ''
file_extensions: 'png gif jpg jpeg'
max_filesize: ''
max_resolution: ''
min_resolution: ''
alt_field: true
alt_field_required: false
title_field: false
title_field_required: false
default_image:
uuid: ''
alt: ''
title: ''
width: null
height: null
handler: 'default:file'
handler_settings: { }
field_type: image
@@ -0,0 +1,27 @@
langcode: en
status: true
dependencies:
config:
- field.storage.user.field_migrate_example_favbeers
- node.type.migrate_example_beer
module:
- user
id: user.user.field_migrate_example_favbeers
field_name: field_migrate_example_favbeers
entity_type: user
bundle: user
label: 'Favorite Beers'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:node'
handler_settings:
target_bundles:
migrate_example_beer: migrate_example_beer
sort:
field: title
direction: ASC
field_type: entity_reference
@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
config:
- field.storage.user.field_migrate_example_gender
module:
- user
id: user.user.field_migrate_example_gender
field_name: field_migrate_example_gender
entity_type: user
bundle: user
label: Gender
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: string
@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
module:
- comment
- node
id: node.field_comments
field_name: field_comments
entity_type: node
type: comment
settings:
comment_type: node_comments
module: comment
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
module:
- node
- taxonomy
id: node.field_migrate_example_beer_style
field_name: field_migrate_example_beer_style
entity_type: node
type: entity_reference
settings:
target_type: taxonomy_term
module: core
locked: false
cardinality: -1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
module:
- node
id: node.field_migrate_example_country
field_name: field_migrate_example_country
entity_type: node
type: string
settings:
max_length: 255
is_ascii: false
case_sensitive: false
module: core
locked: false
cardinality: -1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,29 @@
langcode: en
status: true
dependencies:
module:
- file
- image
- node
id: node.field_migrate_example_image
field_name: field_migrate_example_image
entity_type: node
type: image
settings:
uri_scheme: public
default_image:
uuid: ''
alt: ''
title: ''
width: null
height: null
target_type: file
display_field: false
display_default: false
module: image
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
module:
- node
- user
id: user.field_migrate_example_favbeers
field_name: field_migrate_example_favbeers
entity_type: user
type: entity_reference
settings:
target_type: node
module: core
locked: false
cardinality: -1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
module:
- user
id: user.field_migrate_example_gender
field_name: field_migrate_example_gender
entity_type: user
type: string
settings:
max_length: 255
is_ascii: false
case_sensitive: false
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,9 @@
langcode: en
status: true
name: Beer
type: migrate_example_beer
description: 'Beer is what we drink.'
help: ''
new_revision: false
preview_mode: 1
display_submitted: true
@@ -0,0 +1,8 @@
langcode: en
status: true
dependencies: { }
name: 'Migrate Example Beer Styles'
vid: migrate_example_beer_styles
description: 'Use tags to group beers on similar topics into categories.'
hierarchy: 0
weight: 0
@@ -0,0 +1,18 @@
type: module
name: Migrate Example Setup
description: 'Separate site configuration for the example from the actual migration.'
package: Migration
# core: 8.x
hidden: 1
dependencies:
- drupal:comment
- drupal:image
- drupal:text
- drupal:options
- drupal:taxonomy
# Information added by Drupal.org packaging script on 2017-05-10
version: '8.x-4.0-beta1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1494450189
@@ -0,0 +1,345 @@
<?php
/**
* @file
* Set up source data and destination configuration for the migration example
* module. We do this in a separate module so migrate_example itself is a pure
* migration module.
*/
function migrate_example_setup_schema() {
$schema['migrate_example_beer_account'] = migrate_example_beer_schema_account();
$schema['migrate_example_beer_node'] = migrate_example_beer_schema_node();
$schema['migrate_example_beer_comment'] = migrate_example_beer_schema_comment();
$schema['migrate_example_beer_topic'] = migrate_example_beer_schema_topic();
$schema['migrate_example_beer_topic_node'] = migrate_example_beer_schema_topic_node();
return $schema;
}
function migrate_example_setup_install() {
// Populate our tables.
migrate_example_beer_data_account();
migrate_example_beer_data_node();
migrate_example_beer_data_comment();
migrate_example_beer_data_topic();
migrate_example_beer_data_topic_node();
}
function migrate_example_beer_schema_node() {
return array(
'description' => 'Beers of the world.',
'fields' => array(
'bid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Beer ID.',
),
'name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'body' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Full description of the beer.',
),
'excerpt' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Abstract for this beer.',
),
'countries' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Countries of origin. Multiple values, delimited by pipe',
),
'aid' => array(
'type' => 'int',
'not null' => FALSE,
'description' => 'Account Id of the author.',
),
'image' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Image path',
),
'image_alt' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Image ALT',
),
'image_title' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Image title',
),
'image_description' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Image description',
),
),
'primary key' => array('bid'),
);
}
function migrate_example_beer_schema_topic() {
return array(
'description' => 'Categories',
'fields' => array(
'style' => array(
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
),
'details' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
'style_parent' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Parent topic, if any',
),
'region' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Region first associated with this style',
),
'hoppiness' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Relative hoppiness of the beer',
),
),
'primary key' => array('style'),
);
}
function migrate_example_beer_schema_topic_node() {
return array(
'description' => 'Beers topic pairs.',
'fields' => array(
'bid' => array(
'type' => 'int',
'not null' => TRUE,
'description' => 'Beer ID.',
),
'style' => array(
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'description' => 'Topic name',
),
),
'primary key' => array('style', 'bid'),
);
}
function migrate_example_beer_schema_comment() {
return array(
'description' => 'Beers comments.',
'fields' => array(
'cid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Comment ID.',
),
'bid' => array(
'type' => 'int',
'not null' => TRUE,
'description' => 'Beer ID that is being commented upon',
),
'cid_parent' => array(
'type' => 'int',
'not null' => FALSE,
'description' => 'Parent comment ID in case of comment replies.',
),
'subject' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Comment subject',
),
'body' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Comment body',
),
'name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Comment name (if anon)',
),
'mail' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Comment email (if anon)',
),
'aid' => array(
'type' => 'int',
'not null' => FALSE,
'description' => 'Account ID (if any).',
),
),
'primary key' => array('cid'),
);
}
function migrate_example_beer_schema_account() {
return array(
'description' => 'Beers accounts.',
'fields' => array(
'aid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Account ID',
),
'status' => array(
'type' => 'int',
'not null' => TRUE,
'description' => 'Blocked_Allowed',
),
'registered' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => 'Registration date',
),
'username' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Account name (for login)',
),
'nickname' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Account name (for display)',
),
'password' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Account password (raw)',
),
'email' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Account email',
),
'sex' => array(
'type' => 'int',
'not null' => FALSE,
'description' => 'Gender (0 for male, 1 for female)',
),
'beers' => array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'description' => 'Favorite Beers',
),
),
'primary key' => array('aid'),
);
}
function migrate_example_beer_data_node() {
$fields = array('bid', 'name', 'body', 'excerpt', 'countries', 'aid', 'image',
'image_alt', 'image_title', 'image_description');
$query = db_insert('migrate_example_beer_node')
->fields($fields);
// Use high bid numbers to avoid overwriting an existing node id.
$data = array(
array(99999999, 'Heineken', 'Blab Blah Blah Green', 'Green', 'Netherlands|Belgium', 0, 'heineken.jpg', 'Heinekin alt', 'Heinekin title', 'Heinekin description'), // comes with migrate_example project.
array(99999998, 'Miller Lite', 'We love Miller Brewing', 'Tasteless', 'USA|Canada', 1, NULL, NULL, NULL, NULL),
array(99999997, 'Boddington', 'English occasionally get something right', 'A treat', 'United Kingdom', 1, NULL, NULL, NULL, NULL),
);
foreach ($data as $row) {
$query->values(array_combine($fields, $row));
}
$query->execute();
}
// Note that alice has duplicate username. Exercises dedupe_entity plugin.
// @TODO duplicate email also.
function migrate_example_beer_data_account() {
$fields = array('status', 'registered', 'username', 'nickname', 'password', 'email', 'sex', 'beers');
$query = db_insert('migrate_example_beer_account')
->fields($fields);
$data = array(
array(1, '2010-03-30 10:31:05', 'alice', 'alice in beerland', 'alicepass', 'alice@example.com', '1', '99999999|99999998|99999997'),
array(1, '2010-04-04 10:31:05', 'alice', 'alice in aleland', 'alicepass', 'alice2@example.com', '1', '99999999|99999998|99999997'),
array(0, '2007-03-15 10:31:05', 'bob', 'rebob', 'bobpass', 'bob@example.com', '0', '99999999|99999997'),
array(1, '2004-02-29 10:31:05', 'charlie', 'charlie chocolate', 'mykids', 'charlie@example.com', '0', '99999999|99999998'),
);
foreach ($data as $row) {
$query->values(array_combine($fields, $row));
}
$query->execute();
}
function migrate_example_beer_data_comment() {
$fields = array('bid', 'cid_parent', 'subject', 'body', 'name', 'mail', 'aid');
$query = db_insert('migrate_example_beer_comment')
->fields($fields);
$data = array(
array(99999998, NULL, 'im first', 'full body', 'alice', 'alice@example.com', 0),
array(99999998, NULL, 'im second', 'aromatic', 'alice', 'alice@example.com', 0),
array(99999999, NULL, 'im parent', 'malty', 'alice', 'alice@example.com', 0),
array(99999999, 1, 'im child', 'cold body', 'bob', NULL, 1),
array(99999999, 4, 'im grandchild', 'bitter body', 'charlie@example.com', NULL, 1),
);
foreach ($data as $row) {
$query->values(array_combine($fields, $row));
}
$query->execute();
}
function migrate_example_beer_data_topic() {
$fields = array('style', 'details', 'style_parent', 'region', 'hoppiness');
$query = db_insert('migrate_example_beer_topic')
->fields($fields);
$data = array(
array('ale', 'traditional', NULL, 'Medieval British Isles', 'Medium'),
array('red ale', 'colorful', 'ale', NULL, NULL),
array('pilsner', 'refreshing', NULL, 'Pilsen, Bohemia (now Czech Republic)', 'Low'),
);
foreach ($data as $row) {
$query->values(array_combine($fields, $row));
}
$query->execute();
}
function migrate_example_beer_data_topic_node() {
$fields = array('bid', 'style');
$query = db_insert('migrate_example_beer_topic_node')
->fields($fields);
$data = array(
array(99999999, 'pilsner'),
array(99999999, 'red ale'),
array(99999998, 'red ale'),
);
foreach ($data as $row) {
$query->values(array_combine($fields, $row));
}
$query->execute();
}
@@ -0,0 +1,45 @@
# Migration configuration for beer comments. No new concepts here.
id: beer_comment
label: Comments on beers
migration_group: beer
source:
plugin: beer_comment
destination:
plugin: entity:comment
process:
pid:
plugin: migration
migration: beer_comment
source: cid_parent
entity_id:
plugin: migration
migration: beer_node
source: bid
entity_type:
plugin: default_value
default_value: node
field_name:
plugin: default_value
default_value: field_comments
comment_type:
plugin: default_value
default_value: node_comments
subject: subject
uid:
plugin: migration
migration: beer_user
source: aid
name: name
mail: mail
status:
plugin: default_value
default_value: 1
'comment_body/value': body
migration_dependencies:
required:
- beer_node
- beer_user
dependencies:
enforced:
module:
- migrate_example
@@ -0,0 +1,56 @@
<?php
namespace Drupal\migrate_example\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
/**
* Source plugin for beer comments.
*
* @MigrateSource(
* id = "beer_comment"
* )
*/
class BeerComment extends SqlBase {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('migrate_example_beer_comment', 'mec')
->fields('mec', ['cid', 'cid_parent', 'name', 'mail', 'aid',
'body', 'bid', 'subject'])
->orderBy('cid_parent', 'ASC');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'cid' => $this->t('Comment ID'),
'cid_parent' => $this->t('Parent comment ID in case of comment replies'),
'name' => $this->t('Comment name (if anon)'),
'mail' => $this->t('Comment email (if anon)'),
'aid' => $this->t('Account ID (if any)'),
'bid' => $this->t('Beer ID that is being commented upon'),
'subject' => $this->t('Comment subject'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'cid' => [
'type' => 'integer',
'alias' => 'mec',
],
];
}
}
@@ -0,0 +1,99 @@
<?php
namespace Drupal\migrate_example\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
use Drupal\migrate\Row;
/**
* Source plugin for beer content.
*
* @MigrateSource(
* id = "beer_node"
* )
*/
class BeerNode extends SqlBase {
/**
* {@inheritdoc}
*/
public function query() {
/**
* An important point to note is that your query *must* return a single row
* for each item to be imported. Here we might be tempted to add a join to
* migrate_example_beer_topic_node in our query, to pull in the
* relationships to our categories. Doing this would cause the query to
* return multiple rows for a given node, once per related value, thus
* processing the same node multiple times, each time with only one of the
* multiple values that should be imported. To avoid that, we simply query
* the base node data here, and pull in the relationships in prepareRow()
* below.
*/
$query = $this->select('migrate_example_beer_node', 'b')
->fields('b', ['bid', 'name', 'body', 'excerpt', 'aid',
'countries', 'image', 'image_alt', 'image_title',
'image_description']);
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'bid' => $this->t('Beer ID'),
'name' => $this->t('Name of beer'),
'body' => $this->t('Full description of the beer'),
'excerpt' => $this->t('Abstract for this beer'),
'aid' => $this->t('Account ID of the author'),
'countries' => $this->t('Countries of origin. Multiple values, delimited by pipe'),
'image' => $this->t('Image path'),
'image_alt' => $this->t('Image ALT'),
'image_title' => $this->t('Image title'),
'image_description' => $this->t('Image description'),
// Note that this field is not part of the query above - it is populated
// by prepareRow() below. You should document all source properties that
// are available for mapping after prepareRow() is called.
'terms' => $this->t('Applicable styles'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'bid' => [
'type' => 'integer',
'alias' => 'b',
],
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
/**
* As explained above, we need to pull the style relationships into our
* source row here, as an array of 'style' values (the unique ID for
* the beer_term migration).
*/
$terms = $this->select('migrate_example_beer_topic_node', 'bt')
->fields('bt', ['style'])
->condition('bid', $row->getSourceProperty('bid'))
->execute()
->fetchCol();
$row->setSourceProperty('terms', $terms);
// As we did for favorite beers in the user migration, we need to explode
// the multi-value country names.
if ($value = $row->getSourceProperty('countries')) {
$row->setSourceProperty('countries', explode('|', $value));
}
return parent::prepareRow($row);
}
}
@@ -0,0 +1,88 @@
<?php
namespace Drupal\migrate_example\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
/**
* This is an example of a simple SQL-based source plugin. Source plugins are
* classes which deliver source data to the processing pipeline. For SQL
* sources, the SqlBase class provides most of the functionality needed - for
* a specific migration, you are required to implement the three simple public
* methods you see below.
*
* This annotation tells Drupal that the name of the MigrateSource plugin
* implemented by this class is "beer_term". This is the name that the migration
* configuration references with the source "plugin" key.
*
* @MigrateSource(
* id = "beer_term"
* )
*/
class BeerTerm extends SqlBase {
/**
* {@inheritdoc}
*/
public function query() {
/**
* The most important part of a SQL source plugin is the SQL query to
* retrieve the data to be imported. Note that the query is not executed
* here - the migration process will control execution of the query. Also
* note that it is constructed from a $this->select() call - this ensures
* that the query is executed against the database configured for this
* source plugin.
*/
return $this->select('migrate_example_beer_topic', 'met')
->fields('met', ['style', 'details', 'style_parent', 'region', 'hoppiness'])
// We sort this way to ensure parent terms are imported first.
->orderBy('style_parent', 'ASC');
}
/**
* {@inheritdoc}
*/
public function fields() {
/**
* This method simply documents the available source fields provided by
* the source plugin, for use by front-end tools. It returns an array keyed
* by field/column name, with the value being a translated string explaining
* to humans what the field represents. You should always
*/
$fields = [
'style' => $this->t('Account ID'),
'details' => $this->t('Blocked/Allowed'),
'style_parent' => $this->t('Registered date'),
// These values are not currently migrated - it's OK to skip fields you
// don't need.
'region' => $this->t('Region the style is associated with'),
'hoppiness' => $this->t('Hoppiness of the style'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
/**
* This method indicates what field(s) from the source row uniquely identify
* that source row, and what their types are. This is critical information
* for managing the migration. The keys of the returned array are the field
* names from the query which comprise the unique identifier. The values are
* arrays indicating the type of the field, used for creating compatible
* columns in the map tables that track processed items.
*/
return [
'style' => [
'type' => 'string',
// 'alias' is the alias for the table containing 'style' in the query
// defined above. Optional in this case, but necessary if the same
// column may occur in multiple tables in a join.
'alias' => 'met',
],
];
}
}
@@ -0,0 +1,85 @@
<?php
namespace Drupal\migrate_example\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
use Drupal\migrate\Row;
/**
* Source plugin for beer user accounts.
*
* @MigrateSource(
* id = "beer_user"
* )
*/
class BeerUser extends SqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('migrate_example_beer_account', 'mea')
->fields('mea', ['aid', 'status', 'registered', 'username', 'nickname',
'password', 'email', 'sex', 'beers']);
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'aid' => $this->t('Account ID'),
'status' => $this->t('Blocked/Allowed'),
'registered' => $this->t('Registered date'),
'username' => $this->t('Account name (for login)'),
'nickname' => $this->t('Account name (for display)'),
'password' => $this->t('Account password (raw)'),
'email' => $this->t('Account email'),
'sex' => $this->t('Gender'),
'beers' => $this->t('Favorite beers, pipe-separated'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'aid' => [
'type' => 'integer',
'alias' => 'mea',
],
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
/**
* prepareRow() is the most common place to perform custom run-time
* processing that isn't handled by an existing process plugin. It is called
* when the raw data has been pulled from the source, and provides the
* opportunity to modify or add to that data, creating the canonical set of
* source data that will be fed into the processing pipeline.
*
* In our particular case, the list of a user's favorite beers is a pipe-
* separated list of beer IDs. The processing pipeline deals with arrays
* representing multi-value fields naturally, so we want to explode that
* string to an array of individual beer IDs.
*/
if ($value = $row->getSourceProperty('beers')) {
$row->setSourceProperty('beers', explode('|', $value));
}
/**
* Always call your parent! Essential processing is performed in the base
* class. Be mindful that prepareRow() returns a boolean status - if FALSE
* that indicates that the item being processed should be skipped. Unless
* we're deciding to skip an item ourselves, let the parent class decide.
*/
return parent::prepareRow($row);
}
}
@@ -0,0 +1,58 @@
INTRODUCTION
------------
The migrate_example_advanced module demonstrates some techniques for Drupal 8
migrations beyond the basics in migrate_example. It includes a group of
migrations with a wine theme.
SETUP
-----
To demonstrate XML migrations as realistically as possible, the setup module
provides the source data as REST services. So the migrations' references to these
services can be set up accurately, if you install migrate_example_advanced via
drush be sure to use the --uri parameter, e.g.
drush en -y migrate_example_advanced --uri=http://d8.local:8083/
THE WINE SITE
-------------
In this scenario, we have a wine aficionado site which stores data in SQL tables
as well is pulling in additional data from XML services.
To make the example as simple as to run as possible, the SQL data is placed in
tables directly in your Drupal database - in most real-world scenarios, your
source data will be in an external database. The migrate_example_advanced_setup
submodule creates and populates these tables, as well as configuring your Drupal
8 site (creating node types, vocabularies, fields, etc.) to receive the data,
and providing service endpoints for XML data.
STRUCTURE
---------
As with most custom migrations, there are two primary components to this
example:
1. Migration configuration, in the config/install directory. These YAML files
describe the migration process and provide the mappings from the source data
to Drupal's destination entities.
2. Source plugins, in src/Plugin/migrate/source. These are referenced from the
configuration files, and provide the source data to the migration processing
pipeline, as well as manipulating that data where necessary to put it into
a canonical form for migrations.
UNDERSTANDING THE MIGRATIONS
----------------------------
Basic techniques demonstrated in the migrate_example module are not rehashed
here - it is expected that if you are learning Drupal 8 migration, you will
study and understand those examples first, and use migrate_example_advanced to
learn about specific techniques beyond those basics. This example doesn't have
the narrative form of migrate_example - it's more of a grab-bag demonstrating
varous features, and is more of a reference for, say, copying the code to set
up an XML migration. An index of things demonstrated by this module:
Multiple vocabularies populated in one migration
------------------------------------------------
See migrate_plus.migration.wine_terms.yml.
Importing from XML services
---------------------------
See migrate_plus.migration.wine_role_xml.yml.
@@ -0,0 +1,50 @@
# This migration demonstrates importing from SOAP/WSDL.
id: weather_soap
label: SOAP service providing weather.
migration_group: wine
source:
# We use the SOAP parser source plugin.
plugin: url
data_fetcher_plugin: http # Ignored - SoapClient does the fetching.
data_parser_plugin: soap
# URL of a WSDL endpoint.
urls: http://www.webservicex.net/globalweather.asmx?WSDL
# The function to call on the service, and the parameters to pass. See
# http://www.webservicex.net/New/Home/ServiceDetail/56 for the XML structure
# of this feed - how CountryName is passed within the GetCitiesByCountry
# XML element.
function: GetCitiesByCountry
parameters:
CountryName: Spain
# Responses may be returned as an XML string, an object, or an array - specify
# the type of response here.
response_type: xml
# Looking at the XML response at http://www.webservicex.net/globalweather.asmx/GetCitiesByCountry,
# we see that the data items we want are within <NewDataSet><Table>.
item_selector: /NewDataSet/Table
# For each field, 'name' is the source property name to be used in the process
# steps below, 'label' is optional (to document the property), and selector
# is an xpath (-like, for array and object returns) string relative to the
# item_selector for retrieving that data value.
fields:
-
name: Country
label: Country
selector: Country
-
name: City
label: City
selector: City
# 'ids' tells us what source property ('City') holds the unique identifying
# value for each imported item, and what schema type to use to hold that
# value in the migration map an message tables.
ids:
City:
type: string
process:
vid:
plugin: default_value
default_value: migrate_example_wine_varieties
name: City
destination:
plugin: entity:taxonomy_term
@@ -0,0 +1,55 @@
# This migration demonstrates importing from a monolithic JSON file.
id: wine_role_json
label: JSON feed of roles (positions)
migration_group: wine
source:
# We use the JSON source plugin.
plugin: url
data_fetcher_plugin: http
data_parser_plugin: json
# The data_parser normally limits the fields passed on to the source plugin
# to fields configured to be used as part of the migration. To support more
# dynamic migrations, the JSON data parser supports including the original
# data for the current row. Simply include the 'include_raw_data' flag set
# to `true` to enable this. This option is disabled by default to minimize
# memory footprint for migrations that do not need this capability.
# include_raw_data: true
# Normally, this is one or more fully-qualified URLs or file paths. Because
# we can't hardcode your local URL, we provide a relative path here which
# hook_install() will rewrite to a full URL for the current site.
urls: /migrate_example_advanced_position?_format=json
# An xpath-like selector corresponding to the items to be imported.
item_selector: position
# Under 'fields', we list the data items to be imported. The first level keys
# are the source field names we want to populate (the names to be used as
# sources in the process configuration below). For each field we're importing,
# we provide a label (optional - this is for display in migration tools) and
# an xpath for retrieving that value. It's important to note that this xpath
# is relative to the elements retrieved by item_selector.
fields:
-
name: machine_name
label: 'Unique position identifier'
selector: sourceid
-
name: friendly_name
label: 'Position name'
selector: name
# Under 'ids', we identify source fields populated above which will uniquely
# identify each imported item. The 'type' makes sure the migration map table
# uses the proper schema type for stored the IDs.
ids:
machine_name:
type: string
process:
# Note that the source field names here (machine_name and friendly_name) were
# defined by the 'fields' configuration for the source plugin above.
id: machine_name
label: friendly_name
destination:
plugin: entity:user_role
migration_dependencies: {}
dependencies:
enforced:
module:
- migrate_example_advanced
@@ -0,0 +1,51 @@
# This migration demonstrates importing from a monolithic XML file.
id: wine_role_xml
label: XML feed of roles (positions)
migration_group: wine
source:
# We use the XML data parser plugin.
plugin: url
data_fetcher_plugin: http
data_parser_plugin: xml
# Normally, this is one or more fully-qualified URLs or file paths. Because
# we can't hardcode your local URL, we provide a relative path here which
# hook_install() will rewrite to a full URL for the current site.
urls: /migrate_example_advanced_position?_format=xml
# Visit the URL above (relative to your site root) and look at it. You can see
# that <response> is the outer element, and each item we want to import is a
# <position> element. The item_xpath value is the xpath to use to query the
# desired elements.
item_selector: /response/position
# Under 'fields', we list the data items to be imported. The first level keys
# are the source field names we want to populate (the names to be used as
# sources in the process configuration below). For each field we're importing,
# we provide a label (optional - this is for display in migration tools) and
# an xpath for retrieving that value. It's important to note that this xpath
# is relative to the elements retrieved by item_xpath.
fields:
-
name: machine_name
label: 'Unique position identifier'
selector: sourceid
-
name: friendly_name
label: 'Position name'
selector: name
# Under 'ids', we identify source fields populated above which will uniquely
# identify each imported item. The 'type' makes sure the migration map table
# uses the proper schema type for stored the IDs.
ids:
machine_name:
type: string
process:
# Note that the source field names here (machine_name and friendly_name) were
# defined by the 'fields' configuration for the source plugin above.
id: machine_name
label: friendly_name
destination:
plugin: entity:user_role
migration_dependencies: {}
dependencies:
enforced:
module:
- migrate_example_advanced
@@ -0,0 +1,37 @@
id: wine_terms
label: Migrate all categories into Drupal taxonomy terms
migration_group: wine
source:
plugin: wine_term
destination:
plugin: entity:taxonomy_term
process:
name: name
description: details
# Usually, one wants to have a separate migration for each entity_type/bundle
# combination - e.g., separate migrations for articles and blog posts, as
# opposed to a single monolithic node migration. This affords maximum
# control - the ability to import just one bundle at a time, and most
# importantly to have distinct field mappings (because different node types
# usually have different fields). In this case, though, because all of the
# vocabularies we're importing come from a common table, and on the Drupal
# side there are no distinct custom fields, we are able to import them all in
# one migration. The static_map tells the migration in which vocabulary to
# place each term, based on the incoming 'type' column.
vid:
plugin: static_map
source: type
map:
best_with: migrate_example_wine_best_with
region: migrate_example_wine_regions
variety: migrate_example_wine_varieties
parent:
plugin: migration
migration: wine_terms
source: category_parent
weight: ordering
migration_dependencies: {}
dependencies:
enforced:
module:
- migrate_example_advanced
@@ -0,0 +1,56 @@
# This migration demonstrates importing from an endpoint listing other endpoints
# containing individual item data.
id: wine_variety_list
label: XML feed of varieties
migration_group: wine
source:
# We use the XML source plugin.
plugin: xml
# Normally, this is one or more fully-qualified URLs or file paths. Because
# we can't hardcode your local URL, we provide a relative path here which
# hook_install() will rewrite to a full URL for the current site.
urls: /migrate_example_advanced_variety_list?_format=xml
item_url: /migrate_example_advanced_variety_list/:id?_format=xml
id_selector: /response/items
# Visit the URL above (relative to your site root) and look at it. You can see
# that <response> is the outer element, and each item we want to import is a
# <position> element. The item_xpath value is the xpath to use to query the
# desired elements.
item_selector: /response/variety
# Under 'fields', we list the data items to be imported. The first level keys
# are the source field names we want to populate (the names to be used as
# sources in the process configuration below). For each field we're importing,
# we provide a label (optional - this is for display in migration tools) and
# an xpath for retrieving that value. It's important to note that this xpath
# is relative to the elements retrieved by item_xpath.
fields:
category_name:
label:
selector: name
category_details:
label:
selector: details
category_parent:
label: 'Unique position identifier'
selector: parent
# Under 'ids', we identify source fields populated above which will uniquely
# identify each imported item. The 'type' makes sure the migration map table
# uses the proper schema type for stored the IDs.
ids:
category_name:
type: string
process:
vid:
plugin: default_value
default_value: migrate_example_wine_varieties
name: category_name
description: category_details
parent:
plugin: migration
migration: wine_terms
source: category_parent
destination:
plugin: entity:taxonomy_term
migration_dependencies:
require:
- wine_terms
@@ -0,0 +1,69 @@
# This migration demonstrates importing from multiple XML files.
id: wine_variety_multi_xml
label: XML feed of varieties
migration_group: wine
source:
# We use the XML source plugin.
plugin: url
data_fetcher_plugin: http
data_parser_plugin: xml
# Normally, this is one or more fully-qualified URLs or file paths. Because
# we can't hardcode your local URL, we provide a relative path here which
# hook_install() will rewrite to a full URL for the current site.
urls:
- /migrate_example_advanced_variety_multiple/red?_format=xml
- /migrate_example_advanced_variety_multiple/white?_format=xml
# Visit the URL above (relative to your site root) and look at it. You can see
# that <response> is the outer element, and each item we want to import is a
# <position> element. The item_xpath value is the xpath to use to query the
# desired elements.
item_selector: /response/variety
# Under 'fields', we list the data items to be imported. The first level keys
# are the source field names we want to populate (the names to be used as
# sources in the process configuration below). For each field we're importing,
# we provide a label (optional - this is for display in migration tools) and
# an xpath for retrieving that value. It's important to note that this xpath
# is relative to the elements retrieved by item_xpath.
fields:
-
name: category_name
label: Name
selector: name
-
name: category_details
label: Details
selector: details
-
name: category_parent
label: 'Unique position identifier'
selector: parent
-
name: category_attributes
label: 'List of variety attributes'
selector: attributes
# Under 'ids', we identify source fields populated above which will uniquely
# identify each imported item. The 'type' makes sure the migration map table
# uses the proper schema type for stored the IDs.
ids:
category_name:
type: string
process:
vid:
plugin: default_value
default_value: migrate_example_wine_varieties
name: category_name
description: category_details
parent:
plugin: migration
migration: wine_terms
source: category_parent
field_variety_attributes: category_attributes
destination:
plugin: entity:taxonomy_term
migration_dependencies:
required:
- wine_terms
dependencies:
enforced:
module:
- migrate_example_advanced
@@ -0,0 +1,11 @@
id: wine
label: Wine Imports
description: A few wine-related imports, demonstrating some more advanced migration techniques.
source_type: Custom tables and XML
shared_configuration:
source:
key: default
dependencies:
enforced:
module:
- migrate_example_advanced
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<producer>
<name>Lolonis Winery</name>
<description>Makers of Ladybug Red</description>
<authorid>3</authorid>
<region>Redwood Valley</region>
</producer>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<pr:producer xmlns:pr="http://www.wine.org/wine-producers">
<pr:name>Château Latour</pr:name>
<pr:description>Makers of grand vin Chateau Latour, Les Forts de Latour and Pauillac</pr:description>
<pr:authorid>3</pr:authorid>
<pr:region>Bordeaux</pr:region>
</pr:producer>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<content>
<sourceid>0001</sourceid>
</content>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<wn:content xmlns:wn="http://www.wine.org/wine">
<wn:sourceid>0002</wn:sourceid>
</wn:content>
@@ -0,0 +1,15 @@
type: module
name: Migrate Example (Advanced)
description: 'Specialized examples of Drupal 8 migration.'
package: Examples
# core: 8.x
dependencies:
- drupal:migrate
- migrate_plus:migrate_example_advanced_setup
- migrate_plus:migrate_plus
# Information added by Drupal.org packaging script on 2017-05-10
version: '8.x-4.0-beta1'
core: '8.x'
project: 'migrate_plus'
datestamp: 1494450189
@@ -0,0 +1,50 @@
<?php
/**
* @file
* Install, update and uninstall functions for the migrate_example_advanced module.
*/
use Drupal\migrate_plus\Entity\Migration;
/**
* Implements hook_install().
*/
function migrate_example_advanced_install() {
// We need the urls to be absolute for the XML source plugin to read them, but
// the static configuration files on disk can't know the server and port to
// use. So, in the .yml files we provide the REST resources relative to the
// site root and here rewrite them to fully-qualified paths.
/** @var \Drupal\migrate_plus\Entity\MigrationInterface $wine_role_xml_migration */
$wine_role_xml_migration = Migration::load('wine_role_xml');
if ($wine_role_xml_migration) {
$source = $wine_role_xml_migration->get('source');
$request = \Drupal::request();
$source['urls'] = 'http://' . $request->getHttpHost() . $source['urls'];
$wine_role_xml_migration->set('source', $source);
$wine_role_xml_migration->save();
}
/** @var \Drupal\migrate_plus\Entity\MigrationInterface $wine_role_json_migration */
$wine_role_json_migration = Migration::load('wine_role_json');
if ($wine_role_json_migration) {
$source = $wine_role_json_migration->get('source');
$request = \Drupal::request();
$source['urls'] = 'http://' . $request->getHttpHost() . $source['urls'];
$wine_role_json_migration->set('source', $source);
$wine_role_json_migration->save();
}
/** @var \Drupal\migrate_plus\Entity\MigrationInterface $wine_variety_multi_xml_migration */
$wine_variety_multi_xml_migration = Migration::load('wine_variety_multi_xml');
if ($wine_variety_multi_xml_migration) {
$source = $wine_variety_multi_xml_migration->get('source');
$request = \Drupal::request();
$urls = [];
foreach ($source['urls'] as $url) {
$urls[] = 'http://' . $request->getHttpHost() . $url;
}
$source['urls'] = $urls;
$wine_variety_multi_xml_migration->set('source', $source);
$wine_variety_multi_xml_migration->save();
}
}
@@ -0,0 +1,7 @@
langcode: en
status: true
dependencies: { }
id: migrate_example_advanced_comment
label: 'Node comments'
target_entity_type_id: node
description: ''
@@ -0,0 +1,30 @@
langcode: en
status: true
dependencies:
config:
- comment.type.migrate_example_advanced_comment
- field.field.comment.migrate_example_advanced_comment.comment_body
module:
- text
id: comment.migrate_example_advanced_comment.default
targetEntityType: comment
bundle: migrate_example_advanced_comment
mode: default
content:
author:
weight: -2
comment_body:
type: text_textarea
weight: 11
settings:
rows: 5
placeholder: ''
third_party_settings: { }
subject:
type: string_textfield
weight: 10
settings:
size: 60
placeholder: ''
third_party_settings: { }
hidden: { }
@@ -0,0 +1,69 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.migrate_example_producer.body
- field.field.node.migrate_example_producer.field_migrate_example_wine_regio
- node.type.migrate_example_producer
module:
- path
- text
id: node.migrate_example_producer.default
targetEntityType: node
bundle: migrate_example_producer
mode: default
content:
body:
type: text_textarea_with_summary
weight: 31
settings:
rows: 9
summary_rows: 3
placeholder: ''
third_party_settings: { }
created:
type: datetime_timestamp
weight: 10
settings: { }
third_party_settings: { }
field_migrate_example_wine_regio:
weight: 32
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
type: entity_reference_autocomplete
path:
type: path
weight: 30
settings: { }
third_party_settings: { }
promote:
type: boolean_checkbox
settings:
display_label: true
weight: 15
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
third_party_settings: { }
title:
type: string_textfield
weight: -5
settings:
size: 60
placeholder: ''
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
hidden: { }
@@ -0,0 +1,117 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.migrate_example_wine.body
- field.field.node.migrate_example_wine.field_migrate_example_comments
- field.field.node.migrate_example_wine.field_migrate_example_adv_image
- field.field.node.migrate_example_wine.field_migrate_example_top_vintag
- field.field.node.migrate_example_wine.field_migrate_example_wine_best
- field.field.node.migrate_example_wine.field_migrate_example_wine_ratin
- field.field.node.migrate_example_wine.field_migrate_example_wine_regio
- field.field.node.migrate_example_wine.field_migrate_example_wine_var
- node.type.migrate_example_wine
module:
- comment
- image
- path
- text
id: node.migrate_example_wine.default
targetEntityType: node
bundle: migrate_example_wine
mode: default
content:
body:
type: text_textarea_with_summary
weight: 31
settings:
rows: 9
summary_rows: 3
placeholder: ''
third_party_settings: { }
created:
type: datetime_timestamp
weight: 10
settings: { }
third_party_settings: { }
field_migrate_example_comments:
weight: 38
settings: { }
third_party_settings: { }
type: comment_default
field_migrate_example_adv_image:
weight: 35
settings:
progress_indicator: throbber
preview_image_style: thumbnail
third_party_settings: { }
type: image_image
field_migrate_example_top_vintag:
weight: 37
settings:
placeholder: ''
third_party_settings: { }
type: number
field_migrate_example_wine_best:
weight: 34
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
type: entity_reference_autocomplete_tags
field_migrate_example_wine_ratin:
weight: 36
settings:
placeholder: ''
third_party_settings: { }
type: number
field_migrate_example_wine_regio:
weight: 33
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
type: entity_reference_autocomplete
field_migrate_example_wine_var:
weight: 32
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
type: entity_reference_autocomplete
path:
type: path
weight: 30
settings: { }
third_party_settings: { }
promote:
type: boolean_checkbox
settings:
display_label: true
weight: 15
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
third_party_settings: { }
title:
type: string_textfield
weight: -5
settings:
size: 60
placeholder: ''
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
third_party_settings: { }
hidden: { }
@@ -0,0 +1,41 @@
langcode: en
status: true
dependencies:
config:
- field.field.taxonomy_term.migrate_example_wine_varieties.field_variety_attributes
- taxonomy.vocabulary.migrate_example_wine_varieties
module:
- path
- text
id: taxonomy_term.migrate_example_wine_varieties.default
targetEntityType: taxonomy_term
bundle: migrate_example_wine_varieties
mode: default
content:
description:
type: text_textarea
weight: 1
settings:
placeholder: ''
rows: 5
third_party_settings: { }
field_variety_attributes:
type: string_textfield
weight: 3
settings:
size: 60
placeholder: ''
third_party_settings: { }
name:
type: string_textfield
weight: 0
settings:
size: 60
placeholder: ''
third_party_settings: { }
path:
type: path
weight: 2
settings: { }
third_party_settings: { }
hidden: { }
@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
config:
- comment.type.migrate_example_advanced_comment
- field.field.comment.migrate_example_advanced_comment.comment_body
module:
- text
id: comment.migrate_example_advanced_comment.default
targetEntityType: comment
bundle: migrate_example_advanced_comment
mode: default
content:
comment_body:
label: hidden
type: text_default
weight: 0
settings: { }
third_party_settings: { }
links:
weight: 100
hidden: { }
@@ -0,0 +1,33 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.migrate_example_producer.body
- field.field.node.migrate_example_producer.field_migrate_example_wine_regio
- node.type.migrate_example_producer
module:
- text
- user
id: node.migrate_example_producer.default
targetEntityType: node
bundle: migrate_example_producer
mode: default
content:
body:
label: hidden
type: text_default
weight: 101
settings: { }
third_party_settings: { }
field_migrate_example_wine_regio:
weight: 102
label: inline
settings:
link: true
third_party_settings: { }
type: entity_reference_label
links:
weight: 100
settings: { }
third_party_settings: { }
hidden: { }
@@ -0,0 +1,27 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
- field.field.node.migrate_example_producer.body
- field.field.node.migrate_example_producer.field_migrate_example_wine_regio
- node.type.migrate_example_producer
module:
- text
- user
id: node.migrate_example_producer.teaser
targetEntityType: node
bundle: migrate_example_producer
mode: teaser
content:
body:
label: hidden
type: text_summary_or_trimmed
weight: 101
settings:
trim_length: 600
third_party_settings: { }
links:
weight: 100
hidden:
field_migrate_example_wine_regio: true
@@ -0,0 +1,88 @@
langcode: en
status: true
dependencies:
config:
- field.field.node.migrate_example_wine.body
- field.field.node.migrate_example_wine.field_migrate_example_comments
- field.field.node.migrate_example_wine.field_migrate_example_adv_image
- field.field.node.migrate_example_wine.field_migrate_example_top_vintag
- field.field.node.migrate_example_wine.field_migrate_example_wine_best
- field.field.node.migrate_example_wine.field_migrate_example_wine_ratin
- field.field.node.migrate_example_wine.field_migrate_example_wine_regio
- field.field.node.migrate_example_wine.field_migrate_example_wine_var
- node.type.migrate_example_wine
module:
- comment
- image
- text
- user
id: node.migrate_example_wine.default
targetEntityType: node
bundle: migrate_example_wine
mode: default
content:
body:
label: hidden
type: text_default
weight: 101
settings: { }
third_party_settings: { }
field_migrate_example_comments:
weight: 108
label: hidden
settings:
pager_id: 0
third_party_settings: { }
type: comment_default
field_migrate_example_adv_image:
weight: 105
label: hidden
settings:
image_style: ''
image_link: ''
third_party_settings: { }
type: image
field_migrate_example_top_vintag:
weight: 107
label: inline
settings:
thousand_separator: ''
prefix_suffix: true
third_party_settings: { }
type: number_integer
field_migrate_example_wine_best:
weight: 104
label: inline
settings:
link: true
third_party_settings: { }
type: entity_reference_label
field_migrate_example_wine_ratin:
weight: 106
label: inline
settings:
thousand_separator: ''
decimal_separator: .
scale: 2
prefix_suffix: true
third_party_settings: { }
type: number_decimal
field_migrate_example_wine_regio:
weight: 103
label: inline
settings:
link: true
third_party_settings: { }
type: entity_reference_label
field_migrate_example_wine_var:
weight: 102
label: inline
settings:
link: true
third_party_settings: { }
type: entity_reference_label
links:
weight: 100
settings: { }
third_party_settings: { }
hidden: { }
@@ -0,0 +1,39 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.node.teaser
- field.field.node.migrate_example_wine.body
- field.field.node.migrate_example_wine.field_migrate_example_comments
- field.field.node.migrate_example_wine.field_migrate_example_adv_image
- field.field.node.migrate_example_wine.field_migrate_example_top_vintag
- field.field.node.migrate_example_wine.field_migrate_example_wine_best
- field.field.node.migrate_example_wine.field_migrate_example_wine_ratin
- field.field.node.migrate_example_wine.field_migrate_example_wine_regio
- field.field.node.migrate_example_wine.field_migrate_example_wine_var
- node.type.migrate_example_wine
module:
- text
- user
id: node.migrate_example_wine.teaser
targetEntityType: node
bundle: migrate_example_wine
mode: teaser
content:
body:
label: hidden
type: text_summary_or_trimmed
weight: 101
settings:
trim_length: 600
third_party_settings: { }
links:
weight: 100
hidden:
field_migrate_example_comments: true
field_migrate_example_adv_image: true
field_migrate_example_top_vintag: true
field_migrate_example_wine_best: true
field_migrate_example_wine_ratin: true
field_migrate_example_wine_regio: true
field_migrate_example_wine_var: true
@@ -0,0 +1,27 @@
langcode: en
status: true
dependencies:
config:
- field.field.taxonomy_term.migrate_example_wine_varieties.field_variety_attributes
- taxonomy.vocabulary.migrate_example_wine_varieties
module:
- text
id: taxonomy_term.migrate_example_wine_varieties.default
targetEntityType: taxonomy_term
bundle: migrate_example_wine_varieties
mode: default
content:
description:
label: hidden
type: text_default
weight: 0
settings: { }
third_party_settings: { }
field_variety_attributes:
type: string
weight: 0
label: inline
settings:
link_to_entity: false
third_party_settings: { }
hidden: { }
@@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
config:
- comment.type.migrate_example_advanced_comment
- field.storage.comment.comment_body
module:
- text
id: comment.migrate_example_advanced_comment.comment_body
field_name: comment_body
entity_type: comment
bundle: migrate_example_advanced_comment
label: Comment
description: ''
required: true
translatable: true
default_value: { }
default_value_callback: ''
settings: { }
field_type: text_long
@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.body
- node.type.migrate_example_producer
module:
- text
id: node.migrate_example_producer.body
field_name: body
entity_type: node
bundle: migrate_example_producer
label: Body
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
display_summary: true
field_type: text_with_summary
@@ -0,0 +1,26 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_wine_regio
- node.type.migrate_example_producer
- taxonomy.vocabulary.migrate_example_wine_regions
id: node.migrate_example_producer.field_migrate_example_wine_regio
field_name: field_migrate_example_wine_regio
entity_type: node
bundle: migrate_example_producer
label: 'Migrate Example Wine Regions'
description: 'Select the region this wine comes from'
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
handler: 'default:taxonomy_term'
handler_settings:
target_bundles:
migrate_example_wine_regions: migrate_example_wine_regions
sort:
field: _none
auto_create: false
field_type: entity_reference
@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.body
- node.type.migrate_example_wine
module:
- text
id: node.migrate_example_wine.body
field_name: body
entity_type: node
bundle: migrate_example_wine
label: Body
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
display_summary: true
field_type: text_with_summary
@@ -0,0 +1,37 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_adv_image
- node.type.migrate_example_wine
module:
- image
id: node.migrate_example_wine.field_migrate_example_adv_image
field_name: field_migrate_example_adv_image
entity_type: node
bundle: migrate_example_wine
label: Image
description: 'Upload an image to go with this wine.'
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
file_directory: field/migrate_example/image
file_extensions: 'png gif jpg jpeg'
max_filesize: ''
max_resolution: ''
min_resolution: ''
alt_field: true
alt_field_required: false
title_field: false
title_field_required: false
default_image:
uuid: ''
alt: ''
title: ''
width: null
height: null
handler: 'default:file'
handler_settings: { }
field_type: image
@@ -0,0 +1,32 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_comments
- node.type.migrate_example_wine
module:
- comment
id: node.migrate_example_wine.field_migrate_example_comments
field_name: field_migrate_example_comments
entity_type: node
bundle: migrate_example_wine
label: Comments
description: ''
required: false
translatable: false
default_value:
-
status: 2
cid: 0
last_comment_timestamp: 0
last_comment_name: null
last_comment_uid: 0
comment_count: 0
default_value_callback: ''
settings:
default_mode: 1
per_page: 50
anonymous: 0
form_location: true
preview: 1
field_type: comment
@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_top_vintag
- node.type.migrate_example_wine
id: node.migrate_example_wine.field_migrate_example_top_vintag
field_name: field_migrate_example_top_vintag
entity_type: node
bundle: migrate_example_wine
label: 'Recommended vintages'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
min: null
max: null
prefix: ''
suffix: ''
field_type: integer
@@ -0,0 +1,26 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_wine_best
- node.type.migrate_example_wine
- taxonomy.vocabulary.migrate_example_wine_best_with
id: node.migrate_example_wine.field_migrate_example_wine_best
field_name: field_migrate_example_wine_best
entity_type: node
bundle: migrate_example_wine
label: 'Migrate Example Wine Best With'
description: 'Enter any foods this wine may be paired with, separated by commas'
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
handler: 'default:taxonomy_term'
handler_settings:
target_bundles:
migrate_example_wine_best_with: migrate_example_wine_best_with
sort:
field: _none
auto_create: false
field_type: entity_reference
@@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_migrate_example_wine_ratin
- node.type.migrate_example_wine
id: node.migrate_example_wine.field_migrate_example_wine_ratin
field_name: field_migrate_example_wine_ratin
entity_type: node
bundle: migrate_example_wine
label: Rating
description: 'Rating on a 1-100 scale'
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
min: !!float 1
max: !!float 100
prefix: ''
suffix: ''
field_type: decimal

Some files were not shown because too many files have changed in this diff Show More