updated core and modules

This commit is contained in:
Bachir Soussi Chiadmi
2017-09-09 16:27:51 +02:00
parent 027aa99b32
commit 41863f872c
7810 changed files with 318922 additions and 83631 deletions
@@ -6,8 +6,8 @@ type: module
dependencies:
- toolbar
# Information added by Drupal.org packaging script on 2016-08-30
version: '8.x-1.17'
# Information added by Drupal.org packaging script on 2017-04-06
version: '8.x-1.19'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1472585040
datestamp: 1491487686
@@ -1,5 +1,8 @@
toolbar.tree:
version: VERSION
css:
theme:
css/admin.toolbar.css: {}
js:
js/admin_toolbar.js: {}
dependencies:
- core/jquery
@@ -10,7 +10,7 @@ use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
/**
* Implements hook_toolbar_alter().
* Implements hook_toolbar_alter().
*/
function admin_toolbar_toolbar_alter(&$items) {
$items['administration']['tray']['toolbar_administration']['#pre_render'] = array('admin_toolbar_prerender_toolbar_administration_tray');
@@ -18,7 +18,7 @@ function admin_toolbar_toolbar_alter(&$items) {
}
/**
* Implements hook_help().
* Implements hook_help().
*/
function admin_toolbar_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
@@ -35,8 +35,10 @@ function admin_toolbar_help($route_name, RouteMatchInterface $route_match) {
/**
* Renders the toolbar's administration tray.
*
* This is a clone of core's toolbar_prerender_toolbar_administration_tray()
* function, which uses setMaxDepth(4) instead of setTopLevelOnly()
* function, which uses setMaxDepth(4) instead of setTopLevelOnly().
*
* @param array $element
* A renderable array.
*
@@ -61,7 +63,6 @@ function admin_toolbar_prerender_toolbar_administration_tray(array $element) {
return $element;
}
/**
* Adds toolbar-specific attributes to the menu link tree.
*
@@ -6,8 +6,8 @@ type: module
dependencies:
- admin_toolbar
# Information added by Drupal.org packaging script on 2016-08-30
version: '8.x-1.17'
# Information added by Drupal.org packaging script on 2017-04-06
version: '8.x-1.19'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1472585040
datestamp: 1491487686
@@ -28,7 +28,7 @@ function admin_toolbar_tools_toolbar() {
}
/**
* Implements hook_help().
* Implements hook_help().
*/
function admin_toolbar_tools_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
@@ -43,7 +43,7 @@ function admin_toolbar_tools_help($route_name, RouteMatchInterface $route_match)
}
/**
* Implements hook_menu_links_discovered_alter().
* Implements hook_menu_links_discovered_alter().
*/
function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
$moduleHandler = \Drupal::moduleHandler();
@@ -70,14 +70,35 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
$content_entity_bundle = $entities['content_entity_bundle'];
$content_entity = $entities['content_entity'];
foreach ($entityTypeManager->getStorage($content_entity_bundle)->loadMultiple() as $machine_name => $bundle) {
// Normally, the edit form for the bundle would be its root link.
$content_entity_bundle_root = NULL;
if (in_array('entity.' . $content_entity_bundle . '.overview_form', $routes)) {
// Some bundles have an overview/list form that make a better root link.
$content_entity_bundle_root = 'entity.' . $content_entity_bundle . '.overview_form.' . $machine_name;
$links[$content_entity_bundle_root] = array(
'title' => $bundle->label(),
'route_name' => 'entity.' . $content_entity_bundle . '.overview_form',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'route_parameters' => array($content_entity_bundle => $machine_name),
);
}
if (in_array('entity.' . $content_entity_bundle . '.edit_form', $routes)) {
$links['entity.' . $content_entity_bundle . '.edit_form.' . $machine_name] = array(
$key = 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name;
$links[$key] = array(
'title' => $bundle->label(),
'route_name' => 'entity.' . $content_entity_bundle . '.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'route_parameters' => array($content_entity_bundle => $machine_name),
);
if (empty($content_entity_bundle_root)) {
$content_entity_bundle_root = $key;
}
else {
$links[$key]['parent'] = $content_entity_bundle_root;
$links[$key]['title'] = t('Edit');
}
}
if ($moduleHandler->moduleExists('field_ui')) {
if (in_array('entity.' . $content_entity . '.field_ui_fields', $routes)) {
@@ -85,7 +106,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'title' => t('Manage fields'),
'route_name' => 'entity.' . $content_entity . '.field_ui_fields',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name,
'parent' => $content_entity_bundle_root,
'route_parameters' => array($content_entity_bundle => $machine_name),
'weight' => 1,
);
@@ -95,7 +116,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'title' => t('Manage form display'),
'route_name' => 'entity.entity_form_display.' . $content_entity . '.default',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name,
'parent' => $content_entity_bundle_root,
'route_parameters' => array($content_entity_bundle => $machine_name),
'weight' => 2,
);
@@ -105,7 +126,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'title' => t('Manage display'),
'route_name' => 'entity.entity_view_display.' . $content_entity . '.default',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name,
'parent' => $content_entity_bundle_root,
'route_parameters' => array($content_entity_bundle => $machine_name),
'weight' => 3,
);
@@ -116,7 +137,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'title' => t('Devel'),
'route_name' => 'entity.' . $content_entity_bundle . '.devel_load',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name,
'parent' => $content_entity_bundle_root,
'route_parameters' => array($content_entity_bundle => $machine_name),
'weight' => 4,
);
@@ -126,7 +147,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'title' => t('Delete'),
'route_name' => 'entity.' . $content_entity_bundle . '.delete_form',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name,
'parent' => $content_entity_bundle_root,
'route_parameters' => array($content_entity_bundle => $machine_name),
'weight' => 5,
);
@@ -153,6 +174,12 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'menu_name' => 'admin',
'parent' => 'entity.user.collection',
);
$links['admin_toolbar_tools.user.logout'] = array(
'title' => t('Logout'),
'route_name' => 'user.logout',
'parent' => 'admin_toolbar_tools.help',
'weight' => 10,
);
$links['user.role_add'] = array(
'title' => t('Add a new role'),
'route_name' => 'user.role_add',
@@ -220,16 +247,15 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'route_name' => 'node.type_add',
'menu_name' => 'admin',
'parent' => 'entity.node_type.collection',
'weight' => -5
'weight' => -5,
);
// Add node links for each content type.
foreach (node_type_get_names() as $machine_name => $label) {
$links['node.add.' . $machine_name] = array(
'title' => $label,
foreach (\Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple() as $type) {
$links['node.add.' . $type->id()] = array(
'title' => t($type->label()),
'route_name' => 'node.add',
'menu_name' => 'admin',
'parent' => 'node.add_page',
'route_parameters' => array('node_type' => $machine_name),
'route_parameters' => array('node_type' => $type->id()),
);
}
}
@@ -265,7 +291,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'route_name' => 'entity.menu.add_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.collection',
'weight' => -50
'weight' => -50,
);
// Adds links to /admin/structure/menu.
foreach (menu_ui_get_menus() as $machine_name => $label) {
@@ -309,7 +335,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'route_name' => 'block_content.add_page',
'menu_name' => 'admin',
'parent' => 'block.admin_display',
'weight' => -100
'weight' => -100,
);
$links['entity.block_content.collection'] = array(
'title' => t('Custom block library'),
@@ -332,7 +358,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'route_name' => 'contact.form_add',
'menu_name' => 'admin',
'parent' => 'entity.contact_form.collection',
'weight' => -5
'weight' => -5,
);
}
@@ -455,9 +481,9 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'parent' => 'system.themes_page',
);
// Lists installed themes.
$installed_themes = installedThemes();
$installed_themes = admin_toolbar_tools_installed_themes();
foreach ($installed_themes as $key_theme => $label_theme) {
$links['system.theme_settings_theme' . '.' . $key_theme] = array(
$links['system.theme_settings_theme.' . $key_theme] = array(
'title' => t($label_theme),
'route_name' => 'system.theme_settings_theme',
'menu_name' => 'admin',
@@ -472,9 +498,11 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
/**
* Return installed themes.
*
* @return array
* An array of friendly theme names, keyed by the machine name.
*/
function installedThemes() {
function admin_toolbar_tools_installed_themes() {
$all_themes = \Drupal::service('theme_handler')->listInfo();
$themes_installed = array();
foreach ($all_themes as $key_theme => $theme) {
@@ -487,11 +515,15 @@ function installedThemes() {
}
/**
* Get all links related to entity
* @param $entity_type_id
* Get all links related to entity.
*
* @param string $entity_type_id
* The machine name for the entity type.
*
* @return array
* The links for the entity type.
*/
function getLinks($entity_type_id) {
function admin_toolbar_tools_get_links($entity_type_id) {
$entity = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
// Get all links related to entity.
$links = $entity->getLinkTemplates();
@@ -5,6 +5,7 @@ namespace Drupal\admin_toolbar_tools\Controller;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\CronInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Menu\ContextualLinkManager;
@@ -14,6 +15,7 @@ use Drupal\Core\Menu\MenuLinkManager;
/**
* Class ToolbarController
*
* @package Drupal\admin_toolbar_tools\Controller
*/
class ToolbarController extends ControllerBase {
@@ -64,25 +66,31 @@ class ToolbarController extends ControllerBase {
);
}
// Reload the previous page.
/**
* Reload the previous page.
*/
public function reload_page() {
$request = \Drupal::request();
if($request->server->get('HTTP_REFERER')) {
if ($request->server->get('HTTP_REFERER')) {
return $request->server->get('HTTP_REFERER');
}
else{
else {
return '/';
}
}
// Flushes all caches.
/**
* Flushes all caches.
*/
public function flushAll() {
drupal_flush_all_caches();
drupal_set_message($this->t('All caches cleared.'));
return new RedirectResponse($this->reload_page());
}
// Flushes css and javascript caches.
/**
* Flushes css and javascript caches.
*/
public function flush_js_css() {
\Drupal::state()
->set('system.css_js_query_string', base_convert(REQUEST_TIME, 10, 36));
@@ -90,21 +98,27 @@ class ToolbarController extends ControllerBase {
return new RedirectResponse($this->reload_page());
}
// Flushes plugins caches.
/**
* Flushes plugins caches.
*/
public function flush_plugins() {
\Drupal::service('plugin.cache_clearer')->clearCachedDefinitions();
drupal_set_message($this->t('Plugins cache cleared.'));
return new RedirectResponse($this->reload_page());
}
// Resets all static caches.
/**
* Resets all static caches.
*/
public function flush_static() {
drupal_static_reset();
drupal_set_message($this->t('Static cache cleared.'));
return new RedirectResponse($this->reload_page());
}
// Clears all cached menu data.
/**
* Clears all cached menu data.
*/
public function flush_menu() {
menu_cache_clear_all();
$this->menuLinkManager->rebuild();
@@ -115,38 +129,53 @@ class ToolbarController extends ControllerBase {
return new RedirectResponse($this->reload_page());
}
// Links to drupal.org home page.
/**
* Links to drupal.org home page.
*/
public function drupal_org() {
$response = new RedirectResponse("https://www.drupal.org");
$response = new TrustedRedirectResponse("https://www.drupal.org");
$response->send();
return $response;
}
// Displays the administration link Development.
/**
* 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).
/**
* Access to Drupal 8 changes.
* (list changes of the different versions of drupal core).
*/
public function list_changes() {
$response = new RedirectResponse("https://www.drupal.org/list-changes");
$response = new TrustedRedirectResponse("https://www.drupal.org/list-changes");
$response->send();
return $response;
}
// Adds a link to the Drupal 8 documentation.
/**
* Adds a link to the Drupal 8 documentation.
*/
public function documentation() {
$response = new RedirectResponse("https://api.drupal.org/api/drupal/8");
$response = new TrustedRedirectResponse("https://api.drupal.org/api/drupal/8");
$response->send();
return $response;
}
/**
* Run the cron.
*/
public function runCron() {
$this->cron->run();
drupal_set_message($this->t('Cron ran successfully.'));
return new RedirectResponse($this->reload_page());
}
/**
* Clear the rendered cache.
*/
public function cacheRender() {
$this->cacheRender->invalidateAll();
drupal_set_message($this->t('Render cache cleared.'));
@@ -4,7 +4,6 @@ namespace Drupal\admin_toolbar_tools\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests for the existence of Admin Toolbar tools new links.
*
@@ -31,7 +30,6 @@ class AdminToolbarToolsAlterTest extends WebTestBase {
*/
protected function setUp() {
parent::setUp();
// Create and log in an administrative user.
$this->adminUser = $this->drupalCreateUser([
'access toolbar',
@@ -41,10 +39,11 @@ class AdminToolbarToolsAlterTest extends WebTestBase {
}
/**
* Tests for a the hover of sub menus.
* Tests for the hover of sub menus.
*/
function testAdminToolbarTools() {
public function testAdminToolbarTools() {
// Assert that special menu items are present in the HTML.
$this->assertRaw('class="toolbar-icon toolbar-icon-admin-toolbar-tools-flush"');
}
}
@@ -61,6 +61,7 @@
position: absolute;
width: 200px;
box-shadow: 2px 2px 3px hsla(0, 0%, 0%, 0.4);
z-index: 1;
}
.toolbar-tray-horizontal ul li.menu-item--expanded .menu-item > ul {
@@ -0,0 +1,5 @@
(function($) {
$(document).ready(function() {
$('a.toolbar-icon').removeAttr('title');
});
})(jQuery);
@@ -6,8 +6,8 @@ type: module
dependencies:
- admin_toolbar
# Information added by Drupal.org packaging script on 2016-07-19
version: '8.x-1.3'
# Information added by Drupal.org packaging script on 2017-07-25
version: '8.x-1.5'
core: '8.x'
project: 'adminimal_admin_toolbar'
datestamp: 1468906155
datestamp: 1500955448
@@ -22,10 +22,6 @@
font-family: "Open Sans";
}
.adminimal-admin-toolbar.toolbar-fixed.toolbar-vertical {
padding-top: 32px !important;
}
.adminimal-admin-toolbar .toolbar a {
transition: all, 0.2s;
}
@@ -73,14 +69,14 @@
.adminimal-admin-toolbar .toolbar-tray-horizontal a:hover,
.adminimal-admin-toolbar .toolbar-tray-horizontal a:active,
.adminimal-admin-toolbar .toolbar-tray-horizontal a:focus {
background: #0074bd;
background: #0084d7;
color: #fff;
text-decoration: none;
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover > a,
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover > .toolbar-box > a {
background: #0074bd;
background: #0084d7;
color: #fff !important;
}
@@ -147,7 +143,7 @@
}
.adminimal-admin-toolbar .toolbar .toolbar-icon.toolbar-handle:before {
background-image: url(../images/787878/chevron-outline-disc-down.svg);
background-image: url(../images/acacac/chevron-outline-disc-down.svg);
transition: .25s;
}
@@ -157,7 +153,7 @@
}
.adminimal-admin-toolbar .toolbar .toolbar-menu .toolbar-menu .toolbar-icon.toolbar-handle:before {
background-image: url(../images/787878/chevron-outline-disc-down.svg);
background-image: url(../images/acacac/chevron-outline-disc-down.svg);
background-size: 100%;
}
@@ -193,6 +189,12 @@
background: #545454 !important;
}
/* For WCAG, we are lightening the text on this tier. */
.adminimal-admin-toolbar .toolbar .level-3 a,
.adminimal-admin-toolbar .toolbar .level-4 a {
color: #d5d5d5 !important;
}
.adminimal-admin-toolbar .toolbar-handle.open {
border-top-left-radius: 10px;
}
@@ -218,7 +220,7 @@
.adminimal-admin-toolbar .toolbar .toolbar-tray-vertical .toolbar-menu a:hover,
.adminimal-admin-toolbar .toolbar .toolbar-tray-vertical .toolbar-menu a:focus {
color: #fff;
background: #0074BD;
background: #0084d7;
text-decoration: none;
}
@@ -241,7 +243,8 @@
.adminimal-admin-toolbar .toolbar .menu-item--expanded {
background-color: #333;
}
.adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded {
.adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded,
[dir="rtl"] .adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded {
background-image: none;
position: relative;
}
@@ -253,17 +256,32 @@
.adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:hover ul {
margin: -39px 0 0 200px;
}
[dir="rtl"] .adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:hover ul {
margin: -39px 200px 0 0;
}
.adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded:after {
color: #8eacc0;
content: url(../images/0074bd/chevron-right.svg);
content: url(../images/0084d7/chevron-right.svg);
font-size: 16px;
pointer-events: none;
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-8px);
transition: all .2s
transition: all .2s;
}
[dir="rtl"] .adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded:after {
right: auto;
left: 5px;
top: 30%;
-moz-transform: scaleX(-1);
-o-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
filter: FlipH;
-ms-filter: "FlipH";
}
.adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded:hover:after {
@@ -271,6 +289,16 @@
transform: translate(6px, -8px);
}
[dir="rtl"] .adminimal-admin-toolbar .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded:hover:after {
transform-origin: 40% top;
-moz-transform: scaleX(-1);
-o-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
filter: FlipH;
-ms-filter: "FlipH";
}
.adminimal-admin-toolbar .toolbar .toolbar-menu .toolbar-menu a {
color: #bbb;
}
@@ -292,7 +320,7 @@
}
.adminimal-admin-toolbar .toolbar .toolbar-tray-horizontal ul ul li.menu-item:first-child {
border-top: 1px solid #0074bd;
border-top: 1px solid #0084d7;
}
/* Removes border top on next consecutive lists after first */
@@ -302,7 +330,7 @@
/* Main menu icons. */
.adminimal-admin-toolbar .toolbar-icon-admin-toolbar-tools-help:before {
background-image: url(../images/787878/drupal-8.svg);
background-image: url(../images/acacac/drupal-8.svg);
margin-left: 7px;
padding: 0px;
}
@@ -311,88 +339,98 @@
margin-left: 0;
}
.adminimal-admin-toolbar .toolbar-icon-system-admin-content:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-content.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/file.svg);
.adminimal-admin-toolbar .toolbar-icon-system-admin-content:before {
background-image: url(../images/acacac/file.svg);
}
.adminimal-admin-toolbar .toolbar-icon-system-admin-structure:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-structure.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/orgchart.svg);
.adminimal-admin-toolbar .toolbar-icon-system-admin-structure:before {
background-image: url(../images/acacac/orgchart.svg);
}
.adminimal-admin-toolbar .toolbar-icon-system-themes-page:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-themes-page.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/paintbrush.svg);
.adminimal-admin-toolbar .toolbar-icon-system-themes-page:before {
background-image: url(../images/acacac/paintbrush.svg);
}
.adminimal-admin-toolbar .toolbar-icon-entity-user-collection:active:before,
.adminimal-admin-toolbar .toolbar-icon-entity-user-collection.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/people.svg);
.adminimal-admin-toolbar .toolbar-icon-entity-user-collection:before {
background-image: url(../images/acacac/people.svg);
}
.adminimal-admin-toolbar .toolbar-icon-system-modules-list:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-modules-list.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/puzzlepiece.svg);
.adminimal-admin-toolbar .toolbar-icon-system-modules-list:before {
background-image: url(../images/acacac/puzzlepiece.svg);
}
.adminimal-admin-toolbar .toolbar-icon-system-admin-config:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-config.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/wrench.svg);
.adminimal-admin-toolbar .toolbar-icon-system-admin-config:before {
background-image: url(../images/acacac/wrench.svg);
}
.adminimal-admin-toolbar .toolbar-icon-system-admin-reports:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-reports.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/barchart.svg);
.adminimal-admin-toolbar .toolbar-icon-system-admin-reports:before {
background-image: url(../images/acacac/barchart.svg);
}
.adminimal-admin-toolbar .toolbar-icon-help-main:active:before,
.adminimal-admin-toolbar .toolbar-icon-help-main.is-active:before {
background-image: url(/core/themes/stable/images/core/icons/787878/questionmark-disc.svg);
.adminimal-admin-toolbar .toolbar-icon-help-main:before {
background-image: url(../images/acacac/questionmark-disc.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-admin-toolbar-tools-help:before,
.adminimal-admin-toolbar .toolbar-icon-admin-toolbar-tools-help:hover:before {
.adminimal-admin-toolbar .toolbar-icon-admin-toolbar-tools-help:hover:before,
.adminimal-admin-toolbar .toolbar-icon-admin-toolbar-tools-help:active:before,
.adminimal-admin-toolbar .toolbar-icon-admin-toolbar-tools-help.is-active:before {
background-image: url(../images/ffffff/drupal-8.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-system-admin-content:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-content:hover:before {
.adminimal-admin-toolbar .toolbar-icon-system-admin-content:hover:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-content:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-content.is-active:before {
background-image: url(../images/ffffff/file.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-system-admin-structure:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-structure:hover:before {
.adminimal-admin-toolbar .toolbar-icon-system-admin-structure:hover:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-structure:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-structure.is-active:before {
background-image: url(../images/ffffff/orgchart.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-system-themes-page:before,
.adminimal-admin-toolbar .toolbar-icon-system-themes-page:hover:before {
.adminimal-admin-toolbar .toolbar-icon-system-themes-page:hover:before,
.adminimal-admin-toolbar .toolbar-icon-system-themes-page:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-themes-page.is-active:before {
background-image: url(../images/ffffff/paintbrush.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-entity-user-collection:before,
.adminimal-admin-toolbar .toolbar-icon-entity-user-collection:hover:before {
.adminimal-admin-toolbar .toolbar-icon-entity-user-collection:hover:before,
.adminimal-admin-toolbar .toolbar-icon-entity-user-collection:active:before,
.adminimal-admin-toolbar .toolbar-icon-entity-user-collection.is-active:before {
background-image: url(../images/ffffff/people.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-system-modules-list:before,
.adminimal-admin-toolbar .toolbar-icon-system-modules-list:hover:before {
.adminimal-admin-toolbar .toolbar-icon-system-modules-list:hover:before,
.adminimal-admin-toolbar .toolbar-icon-system-modules-list:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-modules-list.is-active:before {
background-image: url(../images/ffffff/puzzlepiece.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-system-admin-config:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-config:hover:before {
.adminimal-admin-toolbar .toolbar-icon-system-admin-config:hover:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-config:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-config.is-active:before {
background-image: url(../images/ffffff/wrench.svg);
}
.adminimal-admin-toolbar .toolbar-tray-horizontal .menu-item--expanded:hover .toolbar-icon-system-admin-reports:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-reports:hover:before {
.adminimal-admin-toolbar .toolbar-icon-system-admin-reports:hover:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-reports:active:before,
.adminimal-admin-toolbar .toolbar-icon-system-admin-reports.is-active:before {
background-image: url(../images/ffffff/barchart.svg);
}
.adminimal-admin-toolbar .menu-item--expanded:hover .toolbar-icon-help-main:before,
.adminimal-admin-toolbar .toolbar-icon-help-main:hover:before {
.adminimal-admin-toolbar .toolbar-icon-help-main:hover:before,
.adminimal-admin-toolbar .toolbar-icon-help-main:active:before,
.adminimal-admin-toolbar .toolbar-icon-help-main.is-active:before {
background-image: url(../images/ffffff/questionmark-disc.svg);
}
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#0074bd" d="M8.053 8.355c.193-.195.193-.517 0-.711l-3.26-3.289c-.193-.195-.192-.514.002-.709l1.371-1.371c.194-.194.512-.193.706.001l5.335 5.369c.195.195.195.515 0 .708l-5.335 5.37c-.194.192-.512.193-.706.002l-1.371-1.371c-.194-.195-.195-.514-.002-.709l3.26-3.29z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#0084d7" d="M8.053 8.355c.193-.195.193-.517 0-.711l-3.26-3.289c-.193-.195-.192-.514.002-.709l1.371-1.371c.194-.194.512-.193.706.001l5.335 5.369c.195.195.195.515 0 .708l-5.335 5.37c-.194.192-.512.193-.706.002l-1.371-1.371c-.194-.195-.195-.514-.002-.709l3.26-3.29z"/></svg>

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#acacac" d="M4 13.529c0 .275-.225.5-.5.5h-3c-.275 0-.5-.225-.5-.5v-4.25c0-.274.225-.5.5-.5h3c.275 0 .5.226.5.5v4.25zM10.002 13.529c0 .275-.225.5-.5.5h-3.002c-.275 0-.5-.225-.5-.5v-13c0-.275.225-.5.5-.5h3.002c.275 0 .5.225.5.5v13zM16.002 13.529c0 .275-.225.5-.5.5h-3c-.275 0-.5-.225-.5-.5v-9.5c0-.275.225-.5.5-.5h3c.275 0 .5.225.5.5v9.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 428 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#acacac" d="M8.002 1c-3.869 0-7.002 3.134-7.002 7s3.133 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm4.459 6.336l-4.105 4.105c-.196.189-.515.189-.708 0l-4.107-4.105c-.194-.194-.194-.513 0-.707l.977-.978c.194-.194.513-.194.707 0l2.422 2.421c.192.195.513.195.708 0l2.422-2.42c.188-.194.512-.194.707 0l.977.977c.193.194.193.513 0 .707z"/></svg>

After

Width:  |  Height:  |  Size: 416 B

@@ -4,7 +4,7 @@
<svg version="1.0" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#787878" d="M0.045,7.997c0-0.732,0.095-1.438,0.285-2.117C0.52,5.2,0.788,4.565,1.136,3.973
<path fill="#acacac" d="M0.045,7.997c0-0.732,0.095-1.438,0.285-2.117C0.52,5.2,0.788,4.565,1.136,3.973
c0.347-0.592,0.763-1.13,1.248-1.615s1.021-0.9,1.611-1.248c0.589-0.347,1.224-0.615,1.903-0.806
c0.68-0.19,1.385-0.285,2.117-0.285c0.731,0,1.438,0.095,2.117,0.285c0.68,0.19,1.314,0.459,1.903,0.806s1.126,0.763,1.611,1.248
c0.484,0.485,0.9,1.023,1.247,1.615C15.24,4.564,15.509,5.2,15.699,5.88s0.285,1.385,0.285,2.117c0,0.731-0.095,1.437-0.285,2.113
@@ -21,7 +21,7 @@
c-0.506-0.297-1.05-0.527-1.632-0.691S8.643,1.161,8.015,1.161z"/>
</g>
<g>
<path fill="#787878" d="M11.439,10.517l-0.943,0.943L8,8.964L5.504,11.46l-0.943-0.943L7.057,8.02L4.561,5.524l0.943-0.949L8,7.077
<path fill="#acacac" d="M11.439,10.517l-0.943,0.943L8,8.964L5.504,11.46l-0.943-0.943L7.057,8.02L4.561,5.524l0.943-0.949L8,7.077
l2.496-2.502l0.943,0.949L8.943,8.02L11.439,10.517z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#acacac" d="M0.045,7.997c0-0.732,0.095-1.438,0.285-2.117C0.52,5.2,0.788,4.565,1.136,3.973
c0.347-0.592,0.763-1.13,1.248-1.615s1.021-0.9,1.611-1.248c0.589-0.347,1.224-0.615,1.903-0.806
c0.68-0.19,1.385-0.285,2.117-0.285c0.731,0,1.438,0.095,2.117,0.285c0.68,0.19,1.314,0.459,1.903,0.806s1.126,0.763,1.611,1.248
c0.484,0.485,0.9,1.023,1.247,1.615C15.24,4.564,15.509,5.2,15.699,5.88s0.285,1.385,0.285,2.117c0,0.731-0.095,1.437-0.285,2.113
c-0.19,0.678-0.459,1.312-0.806,1.903s-0.763,1.13-1.247,1.615c-0.485,0.484-1.022,0.9-1.611,1.247s-1.224,0.615-1.903,0.806
c-0.68,0.189-1.386,0.285-2.117,0.285c-0.732,0-1.438-0.096-2.117-0.285c-0.68-0.19-1.314-0.459-1.903-0.806
s-1.126-0.763-1.611-1.247c-0.485-0.485-0.901-1.023-1.248-1.615S0.521,10.787,0.33,10.11C0.14,9.434,0.045,8.729,0.045,7.997z
M8.015,1.161c-0.627,0-1.232,0.082-1.814,0.246C5.618,1.571,5.074,1.801,4.568,2.098S3.601,2.751,3.185,3.167
S2.413,4.044,2.116,4.55S1.589,5.6,1.425,6.182C1.26,6.765,1.179,7.37,1.179,7.997c0,0.628,0.082,1.231,0.246,1.814
c0.164,0.582,0.395,1.125,0.691,1.629c0.297,0.503,0.653,0.965,1.069,1.383s0.877,0.774,1.383,1.069
c0.506,0.294,1.05,0.523,1.633,0.688c0.582,0.164,1.187,0.246,1.814,0.246c0.628,0,1.232-0.082,1.814-0.246
s1.126-0.394,1.632-0.688c0.507-0.295,0.968-0.651,1.384-1.069c0.415-0.418,0.771-0.88,1.069-1.383
c0.297-0.504,0.525-1.047,0.688-1.629c0.161-0.583,0.242-1.187,0.242-1.814s-0.081-1.232-0.242-1.814
c-0.162-0.582-0.391-1.126-0.688-1.632c-0.298-0.506-0.654-0.967-1.069-1.383c-0.416-0.416-0.877-0.772-1.384-1.069
c-0.506-0.297-1.05-0.527-1.632-0.691S8.643,1.161,8.015,1.161z"/>
</g>
<g>
<path fill="#acacac" d="M11.439,10.517l-0.943,0.943L8,8.964L5.504,11.46l-0.943-0.943L7.057,8.02L4.561,5.524l0.943-0.949L8,7.077
l2.496-2.502l0.943,0.949L8.943,8.02L11.439,10.517z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -4,7 +4,7 @@
<svg version="1.0" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#787878" d="M0.045,7.997c0-0.732,0.095-1.438,0.285-2.117C0.52,5.2,0.788,4.565,1.136,3.973
<path fill="#acacac" d="M0.045,7.997c0-0.732,0.095-1.438,0.285-2.117C0.52,5.2,0.788,4.565,1.136,3.973
c0.347-0.592,0.763-1.13,1.248-1.615s1.021-0.9,1.611-1.248c0.589-0.347,1.224-0.615,1.903-0.806
c0.68-0.19,1.385-0.285,2.117-0.285c0.731,0,1.438,0.095,2.117,0.285c0.68,0.19,1.314,0.459,1.903,0.806s1.126,0.763,1.611,1.248
c0.484,0.485,0.9,1.023,1.247,1.615C15.24,4.564,15.509,5.2,15.699,5.88s0.285,1.385,0.285,2.117c0,0.731-0.095,1.437-0.285,2.113
@@ -21,6 +21,6 @@
c-0.506-0.297-1.05-0.527-1.632-0.691S8.643,1.161,8.015,1.161z"/>
</g>
<g>
<path fill="#787878" d="M11.664,7.964l-3.649,3.65l-3.65-3.65V5.526l3.65,3.649l3.649-3.649V7.964z"/>
<path fill="#acacac" d="M11.664,7.964l-3.649,3.65l-3.65-3.65V5.526l3.65,3.649l3.649-3.649V7.964z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#acacac" d="M0.045,7.997c0-0.732,0.095-1.438,0.285-2.117C0.52,5.2,0.788,4.565,1.136,3.973
c0.347-0.592,0.763-1.13,1.248-1.615s1.021-0.9,1.611-1.248c0.589-0.347,1.224-0.615,1.903-0.806
c0.68-0.19,1.385-0.285,2.117-0.285c0.731,0,1.438,0.095,2.117,0.285c0.68,0.19,1.314,0.459,1.903,0.806s1.126,0.763,1.611,1.248
c0.484,0.485,0.9,1.023,1.247,1.615C15.24,4.564,15.509,5.2,15.699,5.88s0.285,1.385,0.285,2.117c0,0.731-0.095,1.437-0.285,2.113
c-0.19,0.678-0.459,1.312-0.806,1.903s-0.763,1.13-1.247,1.615c-0.485,0.484-1.022,0.9-1.611,1.247s-1.224,0.615-1.903,0.806
c-0.68,0.189-1.386,0.285-2.117,0.285c-0.732,0-1.438-0.096-2.117-0.285c-0.68-0.19-1.314-0.459-1.903-0.806
s-1.126-0.763-1.611-1.247c-0.485-0.485-0.901-1.023-1.248-1.615S0.521,10.787,0.33,10.11C0.14,9.434,0.045,8.729,0.045,7.997z
M8.015,1.161c-0.627,0-1.232,0.082-1.814,0.246C5.618,1.571,5.074,1.801,4.568,2.098S3.601,2.751,3.185,3.167
S2.413,4.044,2.116,4.55S1.589,5.6,1.425,6.182C1.26,6.765,1.179,7.37,1.179,7.997c0,0.628,0.082,1.231,0.246,1.814
c0.164,0.582,0.395,1.125,0.691,1.629c0.297,0.503,0.653,0.965,1.069,1.383s0.877,0.774,1.383,1.069
c0.506,0.294,1.05,0.523,1.633,0.688c0.582,0.164,1.187,0.246,1.814,0.246c0.628,0,1.232-0.082,1.814-0.246
s1.126-0.394,1.632-0.688c0.507-0.295,0.968-0.651,1.384-1.069c0.415-0.418,0.771-0.88,1.069-1.383
c0.297-0.504,0.525-1.047,0.688-1.629c0.161-0.583,0.242-1.187,0.242-1.814s-0.081-1.232-0.242-1.814
c-0.162-0.582-0.391-1.126-0.688-1.632c-0.298-0.506-0.654-0.967-1.069-1.383c-0.416-0.416-0.877-0.772-1.384-1.069
c-0.506-0.297-1.05-0.527-1.632-0.691S8.643,1.161,8.015,1.161z"/>
</g>
<g>
<path fill="#acacac" d="M11.664,7.964l-3.649,3.65l-3.65-3.65V5.526l3.65,3.649l3.649-3.649V7.964z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#acacac" d="M8.053 8.355c.193-.195.193-.517 0-.711l-3.26-3.289c-.193-.195-.192-.514.002-.709l1.371-1.371c.194-.194.512-.193.706.001l5.335 5.369c.195.195.195.515 0 .708l-5.335 5.37c-.194.192-.512.193-.706.002l-1.371-1.371c-.194-.195-.195-.514-.002-.709l3.26-3.29z"/></svg>

After

Width:  |  Height:  |  Size: 347 B

@@ -4,19 +4,19 @@
viewBox="-249 83.1 381 416" enable-background="new -249 83.1 381 416" xml:space="preserve">
<g>
<g>
<path fill="#787878" d="M-69.7,312.5c-29.1,0-52.7,23.6-52.7,52.7c0,29.1,23.6,52.7,52.7,52.7c29.1,0,52.7-23.6,52.7-52.7
<path fill="#acacac" d="M-69.7,312.5c-29.1,0-52.7,23.6-52.7,52.7c0,29.1,23.6,52.7,52.7,52.7c29.1,0,52.7-23.6,52.7-52.7
C-17,336.1-40.6,312.5-69.7,312.5z"/>
</g>
<g>
<path fill="#787878" d="M-4.1,302.4c15.6,16.3,25.2,38.4,25.2,62.8c0,30.9-15.4,58.1-38.9,74.5c43.5-13.4,79.5-46.1,95.6-85.7
<path fill="#acacac" d="M-4.1,302.4c15.6,16.3,25.2,38.4,25.2,62.8c0,30.9-15.4,58.1-38.9,74.5c43.5-13.4,79.5-46.1,95.6-85.7
c22.3-54.8,1.5-96-33.3-133.4c1.1,4.8,1.7,9.9,1.7,15C46.1,267.4,24.9,294.1-4.1,302.4z"/>
</g>
<g>
<path fill="#787878" d="M-63.7,235.7c0,22.3,18.1,40.3,40.3,40.3S17,257.9,17,235.7c0-22.3-18.1-40.3-40.3-40.3
<path fill="#acacac" d="M-63.7,235.7c0,22.3,18.1,40.3,40.3,40.3S17,257.9,17,235.7c0-22.3-18.1-40.3-40.3-40.3
S-63.7,213.4-63.7,235.7z"/>
</g>
<g>
<path fill="#787878" d="M-139.2,423.7c-13.3-15.8-21.3-36.2-21.3-58.5c0-46.5,34.9-84.8,80-90.1c-7.7-11.2-12.3-24.8-12.3-39.4
<path fill="#acacac" d="M-139.2,423.7c-13.3-15.8-21.3-36.2-21.3-58.5c0-46.5,34.9-84.8,80-90.1c-7.7-11.2-12.3-24.8-12.3-39.4
c0-38.4,31.1-69.4,69.5-69.4c3.2,0,6.3,0.2,9.3,0.6c-22.7-19.6-45.4-39.5-63.3-61.2c9.1,95.2-86.7,60.6-122.2,148.4
C-223.2,312.9-201.8,385.6-139.2,423.7z"/>
</g>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#acacac" d="M12.502 7h-5c-.276 0-.502-.225-.502-.5v-5c0-.275-.225-.5-.5-.5h-3c-.275 0-.5.225-.5.5v12.029c0 .275.225.5.5.5h9.002c.275 0 .5-.225.5-.5v-6.029c0-.275-.225-.5-.5-.5zM8.502 6h4c.275 0 .34-.159.146-.354l-4.293-4.292c-.195-.195-.353-.129-.353.146v4c0 .275.225.5.5.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 366 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px"><path fill="#acacac" d="M15.002,11.277c0-0.721,0-1.471,0-2.014c0-1.456-0.824-2.25-2.25-2.25c-1.428,0-3.5,0-3.5,0c-0.139,0-0.25-0.112-0.25-0.25v-2.04c0.596-0.346,1-0.984,1-1.723c0-1.104-0.895-2-2-2C6.896,1,6,1.896,6,3c0,0.738,0.405,1.376,1,1.722v2.042c0,0.138-0.112,0.25-0.25,0.25c0,0-2.138,0-3.5,0S1,7.932,1,9.266c0,0.521,0,1.277,0,2.012c-0.595,0.353-1,0.984-1,1.729c0,1.104,0.896,2,2,2s2-0.896,2-2c0-0.732-0.405-1.377-1-1.729V9.266c0-0.139,0.112-0.25,0.25-0.25h3.536C6.904,9.034,7,9.126,7,9.25v2.027C6.405,11.624,6,12.26,6,13c0,1.104,0.896,2,2.002,2c1.105,0,2-0.896,2-2c0-0.738-0.404-1.376-1-1.723V9.25c0-0.124,0.098-0.216,0.215-0.234h3.535c0.137,0,0.25,0.111,0.25,0.25v2.012c-0.596,0.353-1,0.984-1,1.729c0,1.104,0.896,2,2,2s2-0.896,2-2C16.002,12.262,15.598,11.623,15.002,11.277z"/></svg>

After

Width:  |  Height:  |  Size: 857 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#acacac" d="M8.184 7.928l-1.905 1.983-3.538-2.436 4.714-4.713 2.623 3.183-1.894 1.983zm-1.746-7.523c-.236-.416-.803-.649-1.346.083-.259.349-4.727 4.764-4.91 4.983-.182.218-.294.721.044.976.34.258 5.611 3.933 5.611 3.933l-.225.229c.7.729.816.854 1.046.863.75.016 2.035-1.457 2.578-.854.541.604 3.537 3.979 3.537 3.979.51.531 1.305.559 1.815.041.521-.521.541-1.311.025-1.848 0 0-2.742-2.635-3.904-3.619-.578-.479.869-2.051.854-2.839-.008-.238-.125-.361-.823-1.095l-.22.243c0 .003-3.846-4.659-4.082-5.075z"/></svg>

After

Width:  |  Height:  |  Size: 587 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#acacac" d="M14.545 3.042l-1.586-1.585c-.389-.389-1.025-.389-1.414 0l-1.293 1.293 3 3 1.293-1.293c.389-.389.389-1.026 0-1.415z"/><rect fill="#acacac" x="5.129" y="3.8" transform="matrix(-.707 -.707 .707 -.707 6.189 20.064)" width="4.243" height="9.899"/><path fill="#acacac" d="M.908 14.775c-.087.262.055.397.316.312l2.001-.667-1.65-1.646-.667 2.001z"/></g></svg>

After

Width:  |  Height:  |  Size: 442 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g><path fill="#acacac" d="M6.722 11.291l.451-.17-.165-.32c-.536-1.039-.685-1.934-.761-2.672-.082-.808-.144-2.831 1.053-4.189.244-.278.493-.493.743-.675.012-.826-.135-1.766-.646-2.345-.618-.7-1.4-.787-1.4-.787l-.497-.055-.498.055s-.783.087-1.398.787c-.617.702-.717 1.948-.625 2.855.06.583.17 1.263.574 2.05.274.533.341.617.355 1.01.022.595.027 1.153-.671 1.538-.697.383-1.564.508-2.403 1.088-.596.41-.709 1.033-.78 1.459l-.051.41c-.029.273.173.498.448.498h5.012c.457-.24.89-.402 1.259-.537zM5.064 15.096c.07-.427.184-1.05.78-1.46.838-.581 1.708-.706 2.404-1.089.699-.385.693-.943.672-1.537-.014-.393-.08-.477-.354-1.01-.406-.787-.515-1.467-.576-2.049-.093-.909.008-2.155.625-2.856.615-.7 1.398-.787 1.398-.787l.492-.055h.002l.496.055s.781.087 1.396.787c.615.701.72 1.947.623 2.855-.062.583-.172 1.262-.571 2.049-.271.533-.341.617-.354 1.01-.021.595-.062 1.22.637 1.604.697.385 1.604.527 2.438 1.104.923.641.822 1.783.822 1.783-.022.275-.269.5-.542.5h-9.991c-.275 0-.477-.223-.448-.496l.051-.408z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#acacac" d="M11.002 11v2.529c0 .275-.225.471-.5.471h-3c-.827 0-1.112-.754-.604-1.316l.81-.725c.509-.562.513-1.461-.097-2.01-.383-.344-1.027-.728-2.111-.728s-1.727.386-2.109.731c-.609.549-.606 1.45-.097 2.015l.808.717c.509.562.223 1.316-.602 1.316h-3c-.276 0-.5-.193-.5-.471v-9.029c0-.276.224-.5.5-.5h3c.825 0 1.111-.768.602-1.332l-.808-.73c-.51-.563-.513-1.465.097-2.014.382-.344 1.025-.731 2.109-.731s1.728.387 2.111.731c.608.548.606 1.45.097 2.014l-.81.73c-.509.564-.223 1.332.603 1.332h3c.274 0 .5.224.5.5v2.5c0 .825.642 1.111 1.233.602l.771-.808c.599-.51 1.547-.513 2.127.097.364.383.772 1.025.772 2.109s-.408 1.727-.771 2.109c-.58.604-1.529.604-2.127.097l-.77-.808c-.593-.509-1.234-.223-1.234.602z"/></svg>

After

Width:  |  Height:  |  Size: 787 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#acacac" d="M8.002 1c-3.868 0-7.002 3.134-7.002 7s3.134 7 7.002 7c3.865 0 7-3.134 7-7s-3.135-7-7-7zm3 5c0 .551-.16 1.085-.477 1.586l-.158.22c-.07.093-.189.241-.361.393-.168.148-.35.299-.545.447l-.203.189-.141.129-.096.17-.021.235v.63h-2.001v-.704c.026-.396.078-.73.204-.999.125-.269.271-.498.439-.688l.225-.21-.01-.015.176-.14.137-.128c.186-.139.357-.277.516-.417l.148-.18c.098-.152.168-.323.168-.518 0-.552-.447-1-1-1s-1.002.448-1.002 1h-2c0-1.657 1.343-3 3.002-3 1.656 0 3 1.343 3 3zm-1.75 6.619c0 .344-.281.625-.625.625h-1.25c-.345 0-.626-.281-.626-.625v-1.238c0-.344.281-.625.626-.625h1.25c.344 0 .625.281.625.625v1.238z"/></svg>

After

Width:  |  Height:  |  Size: 709 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#acacac" d="M14.416 11.586l-.01-.008v-.001l-5.656-5.656c.15-.449.252-.921.252-1.421 0-2.485-2.016-4.5-4.502-4.5-.505 0-.981.102-1.434.255l2.431 2.431-.588 2.196-2.196.588-2.445-2.445c-.162.464-.268.956-.268 1.475 0 2.486 2.014 4.5 4.5 4.5.5 0 .972-.102 1.421-.251l5.667 5.665c.781.781 2.047.781 2.828 0s.781-2.047 0-2.828z"/></svg>

After

Width:  |  Height:  |  Size: 407 B

@@ -3,10 +3,10 @@ type: module
description: 'Provides basic revert and update functionality for other modules'
# core: 8.x
dependencies:
- config
- drupal:config
# Information added by Drupal.org packaging script on 2016-03-08
version: '8.x-1.1'
# Information added by Drupal.org packaging script on 2017-01-18
version: '8.x-1.3'
core: '8.x'
project: 'config_update'
datestamp: 1457466840
datestamp: 1484770686
@@ -4,8 +4,8 @@ services:
arguments: ['@string_translation']
config_update.config_list:
class: Drupal\config_update\ConfigLister
arguments: ['@entity.manager', '@config.storage', '@config_update.extension_storage', '@config_update.extension_optional_storage']
class: Drupal\config_update\ConfigListerWithProviders
arguments: ['@entity.manager', '@config.storage', '@config_update.extension_storage', '@config_update.extension_optional_storage', '@module_handler', '@theme_handler']
config_update.config_update:
class: Drupal\config_update\ConfigReverter
@@ -85,15 +85,16 @@ drush config-list-types (clt)
drush config-added-report (cra)
drush config-missing-report (crm)
drush config-different-report (crd)
drush config-inactive-report (cri)
Run config reports (see below).
drush config-diff (cfd)
Show config differences for one item between active and imported (see below).
The report commands run reports that tell what config has been added, is
missing, or is different between your active site configuration and the imported
default configuration from config/install directories of your installed profile,
modules, and themes.
missing, is inactive, or is different between your active site configuration and
the imported default configuration from config/install directories of your
installed profile, modules, and themes.
For each report except "added", the first argument is one of:
- type: Runs the report for a configuration type; use drush config-list-types to
@@ -108,7 +109,8 @@ module, theme, or install profile you want to run the report for. For the
configuration type.
These are the same as the reports you get in the UI, which is described above;
the only difference is that in Drush the report is separated into pieces.
the difference is that in Drush the report is separated into pieces, and the
UI report sections have more columns giving information about each config item.
Once you have found a configuration item with differences, you can view the
differences using the config-diff command. This is a normalized/formatted diff
@@ -130,16 +132,25 @@ different, you can:
- Export them - see drush config-export.
- Import missing configuration or revert to provided default values. To do this:
- Import missing configuration or revert to provided default values. There are
Drush commands to do this:
(1) Locate the configuration file in the install profile, module, or theme
config/install directory.
drush config-revert (cfr)
Revert a config item found in a "different" report to the current version in
the module, theme, or install profile that provides it.
(2) Copy this file to your configuration staging directory.
drush config-revert-multiple (cfrm)
Revert all config items of one type or from one module, theme, or install
profile to the provided versions.
(3) Run drush config-import. You might want to use the --preview option to see
what differences you are about to import, before running the import, or
use the drush config-diff command to look at individual differences.
drush config-import-missing (cfi)
Import a config item found in a "missing" or "inactive" report from the
current version in the module, theme, or install profile that provides it.
You can also to use the drush config-import command to import/update multiple
configuration items. But this requires that you locate the files containing the
configuration you want to import, and copy them to your configuration staging
directory.
IMPORTANT NOTES
@@ -74,7 +74,7 @@ function config_update_ui_drush_command() {
];
$items['config-different-report'] = [
'description' => 'Display a list of config items that differ from the versions imported from your installed modules, themes, or install profile. See config-diff to show what the differences are.',
'description' => 'Display a list of config items that differ from the versions provided by your installed modules, themes, or install profile. See config-diff to show what the differences are.',
'arguments' => [
'type' => 'Run the report for: module, theme, profile, or "type" for config entity type.',
'name' => 'The machine name of the module, theme, etc. to report on. See config-list-types to list types for config entities; you can also use system.all for all types, or system.simple for simple config.',
@@ -83,7 +83,6 @@ function config_update_ui_drush_command() {
'examples' => [
'drush config-different-report type action' => 'Displays the differing config report for action config.',
],
'drupal dependencies' => ['config_update_ui'],
'aliases' => ['crd'],
'core' => ['8+'],
'outputformat' => [
@@ -104,6 +103,46 @@ function config_update_ui_drush_command() {
'core' => ['8+'],
];
$items['config-revert'] = [
'description' => 'Revert one config item in active storage to the version provided by an installed module, theme, or install profile.',
'arguments' => [
'name' => 'The config item to revert. See config-different-report to list config items that are different.',
],
'required-arguments' => 1,
'examples' => [
'drush config-revert block.block.bartik_search' => 'Revert the config for the search block in the Bartik theme to the version provided by the install profile.',
],
'aliases' => ['cfr'],
'core' => ['8+'],
];
$items['config-import-missing'] = [
'description' => 'Import a missing or inactive config item provided by an installed module, theme, or install profile. Be sure that requirements are met.',
'arguments' => [
'name' => 'The name of the config item to import (usually the ID you would see in the user interface). See config-missing-report to list config items that are missing, and config-inactive-report to list config items that are inactive.',
],
'required-arguments' => 1,
'examples' => [
'drush config-import-missing block.block.bartik_search' => 'Import the config for the search block in the Bartik theme from the version provided by the install profile.',
],
'aliases' => ['cfi'],
'core' => ['8+'],
];
$items['config-revert-multiple'] = [
'description' => 'Revert a set of config items to the versions provided by installed modules, themes, or install profiles. A set is all differing items from one extension, or one type of configuration.',
'arguments' => [
'type' => 'Type of set to revert: "module" for all items from a module, "theme" for all items from a theme, "profile" for all items from the install profile, or "type" for all items of one config entity type. See config-different-report to list config items that are different.',
'name' => 'The machine name of the module, theme, etc. to revert items of. All items in the corresponding config-different-report will be reverted.',
],
'required-arguments' => 2,
'examples' => [
'drush config-revert-multiple type action' => 'Revert all differing config items of type action.',
],
'aliases' => ['cfrm'],
'core' => ['8+'],
];
return $items;
}
@@ -222,11 +261,91 @@ function drush_config_update_ui_config_diff($name) {
$extension = $reverter->getFromExtension('', $name);
$active = $reverter->getFromActive('', $name);
if ($extension && !$active) {
if ($extension && $active) {
$diff = $differ->diff($extension, $active);
drush_print($formatter->format($diff));
$output = $formatter->format($diff);
return $output;
}
else {
drush_print(dt('Config is missing, cannot diff'), 0, STDERR);
return '';
}
}
/**
* Runs the drush config-revert command.
*
* @param string $name
* Config item to revert.
*/
function drush_config_update_ui_config_revert($name) {
$lister = \Drupal::service('config_update.config_list');
$reverter = \Drupal::service('config_update.config_update');
$manager = \Drupal::service('entity.manager');
// The revert command needs the type and the unprefixed name.
$type = $lister->getTypeNameByConfigName($name);
$shortname = $name;
if ($type && $type != 'system.simple') {
$definition = $manager->getDefinition($type);
$prefix = $definition->getConfigPrefix() . '.';
if (strpos($name, $prefix) === 0) {
$shortname = substr($name, strlen($prefix));
}
}
if ($reverter->revert($type, $shortname)) {
drush_print(dt('The configuration item @name was reverted to its source.', ['@name' => $name]));
}
else {
drush_print(dt('There was an error and the configuration item @name was not reverted.', ['@name' => $name]), 0, STDERR);
}
}
/**
* Runs the drush config-import-missing command.
*
* @param string $name
* Name of config item to import, without prefix.
*/
function drush_config_update_ui_config_import_missing($name) {
$lister = \Drupal::service('config_update.config_list');
$reverter = \Drupal::service('config_update.config_update');
$manager = \Drupal::service('entity.manager');
// The import command needs the type and the unprefixed name.
$type = $lister->getTypeNameByConfigName($name);
$shortname = $name;
if ($type && $type != 'system.simple') {
$definition = $manager->getDefinition($type);
$prefix = $definition->getConfigPrefix() . '.';
if (strpos($name, $prefix) === 0) {
$shortname = substr($name, strlen($prefix));
}
}
if ($reverter->import($type, $shortname)) {
drush_print(dt('The configuration item @name was imported from its source.', ['@name' => $name]));
}
else {
drush_print(dt('There was an error and the configuration item @name was not imported.', ['@name' => $name]), 0, STDERR);
drush_print(dt('The configuration was imported from its source.'));
}
}
/**
* Runs the drush config-revert-multiple command.
*
* @param string $type
* Type of config to revert: 'type', 'module', 'theme', or 'profile'.
* @param string $name
* Machine name of type to revert.
*/
function drush_config_update_ui_config_revert_multiple($type, $name) {
// This command will print an error if there are no items to revert.
$different = drush_config_update_ui_config_different_report($type, $name);
foreach ($different as $name) {
// This command will print a message if it succeeds or fails for each item.
drush_config_update_ui_config_revert($name);
}
}
@@ -0,0 +1,16 @@
<?php
/**
* @file
* Defines stubs for Drush functions and constants so tests can run.
*
* @see \Drupal\config_update_ui\Tests\ConfigUpdateTest
*/
if (!function_exists('drush_print')) {
function drush_print() {}
}
if (!function_exists('dt')) {
function dt($text) { return $text; }
}
defined('STDERR') or define('STDERR', 25);
@@ -4,11 +4,11 @@ description: 'Adds an updates report and revert functionality to configuration m
# core: 8.x
configure: config_update.report
dependencies:
- config_update
- config
- config_update:config_update
- drupal:config
# Information added by Drupal.org packaging script on 2016-03-08
version: '8.x-1.1'
# Information added by Drupal.org packaging script on 2017-01-18
version: '8.x-1.3'
core: '8.x'
project: 'config_update'
datestamp: 1457466840
datestamp: 1484770686
@@ -26,7 +26,7 @@ function config_update_ui_help($route_name, RouteMatchInterface $route_match) {
case 'config_update_ui.report':
$output = '';
$output .= '<p>' . t('This report shows which default configuration items provided by the current versions of your installed modules, themes, and install profile differ from the active configuration of your site. You can generate the report for all configuration of a certain type, or for a particular installed module, theme, or your install profile.') . '</p>';
$output .= '<p>' . t('This report shows which default configuration items provided by the current versions of your installed modules, themes, and install profile differ from the active configuration of your site. You can generate the report for all configuration of a certain type, or for a particular installed module, theme, or your install profile (only modules, themes, and install profiles that actually provide configuration are listed).') . '</p>';
$output .= '<p>' . t('Some configuration provided by modules has dependencies, and is only added to your system when all the dependencies are satisfied. This is known as <em>optional</em> configuration.') . '</p>';
$output .= '<p>' . t('Differences, including missing and added configuration, can be due to either an updated module or theme providing different configuration from when you installed it, or to changes made by site administrators. This report does not differentiate between these two sources for differences, and note that an install profile can also silently override module/theme configuration. Overrides (such as those in your settings.php file) and translations are not considered when looking at differences.') . '</p>';
return $output;
@@ -9,7 +9,7 @@ use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\config_update\ConfigDiffInterface;
use Drupal\config_update\ConfigListInterface;
use Drupal\config_update\ConfigListByProviderInterface;
use Drupal\config_update\ConfigRevertInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -28,7 +28,7 @@ class ConfigUpdateController extends ControllerBase {
/**
* The config lister.
*
* @var \Drupal\config_update\ConfigListInterface
* @var \Drupal\config_update\ConfigListByProviderInterface
*/
protected $configList;
@@ -65,7 +65,7 @@ class ConfigUpdateController extends ControllerBase {
*
* @param \Drupal\config_update\ConfigDiffInterface $config_diff
* The config differ.
* @param \Drupal\config_update\ConfigListInterface $config_list
* @param \Drupal\config_update\ConfigListByProviderInterface $config_list
* The config lister.
* @param \Drupal\config_update\ConfigRevertInterface $config_update
* The config reverter.
@@ -76,7 +76,7 @@ class ConfigUpdateController extends ControllerBase {
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(ConfigDiffInterface $config_diff, ConfigListInterface $config_list, ConfigRevertInterface $config_update, DiffFormatter $diff_formatter, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
public function __construct(ConfigDiffInterface $config_diff, ConfigListByProviderInterface $config_list, ConfigRevertInterface $config_update, DiffFormatter $diff_formatter, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
$this->configDiff = $config_diff;
$this->configList = $config_list;
$this->configRevert = $config_update;
@@ -241,7 +241,7 @@ class ConfigUpdateController extends ControllerBase {
$definitions = $this->configList->listTypes();
$links = [];
foreach ($definitions as $entity_type => $definition) {
$links['type_' . $entity_type] = [
$links['report_type_' . $entity_type] = [
'title' => $definition->getLabel(),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'type', 'name' => $entity_type]),
];
@@ -250,11 +250,11 @@ class ConfigUpdateController extends ControllerBase {
uasort($links, [$this, 'sortLinks']);
$links = [
'type_all' => [
'report_type_all' => [
'title' => $this->t('All types'),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'type', 'name' => 'system.all']),
],
'type_system.simple' => [
'report_type_system.simple' => [
'title' => $this->t('Simple configuration'),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'type', 'name' => 'system.simple']),
],
@@ -275,8 +275,8 @@ class ConfigUpdateController extends ControllerBase {
$modules = $this->moduleHandler->getModuleList();
$links = [];
foreach ($modules as $machine_name => $module) {
if ($machine_name != $profile) {
$links['module_' . $machine_name] = [
if ($machine_name != $profile && $this->configList->providerHasConfig('module', $machine_name)) {
$links['report_module_' . $machine_name] = [
'title' => $this->moduleHandler->getName($machine_name),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'module', 'name' => $machine_name]),
];
@@ -298,10 +298,12 @@ class ConfigUpdateController extends ControllerBase {
$themes = $this->themeHandler->listInfo();
$links = [];
foreach ($themes as $machine_name => $theme) {
$links['theme_' . $machine_name] = [
'title' => $this->themeHandler->getName($machine_name),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'theme', 'name' => $machine_name]),
];
if ($this->configList->providerHasConfig('theme', $machine_name)) {
$links['report_theme_' . $machine_name] = [
'title' => $this->themeHandler->getName($machine_name),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'theme', 'name' => $machine_name]),
];
}
}
uasort($links, [$this, 'sortLinks']);
@@ -315,21 +317,24 @@ class ConfigUpdateController extends ControllerBase {
],
];
// Profile is just one option.
$links = [];
$links['profile_' . $profile] = [
'title' => $this->moduleHandler->getName($profile),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'profile']),
];
$build['links']['#rows'][] = [
$this->t('Installation profile'),
[
'data' => [
'#type' => 'operations',
'#links' => $links,
// Profile is just one option.
if ($this->configList->providerHasConfig('profile', $profile)) {
$links['report_profile_' . $profile] = [
'title' => $this->moduleHandler->getName($profile),
'url' => Url::fromRoute('config_update_ui.report', ['report_type' => 'profile']),
];
$build['links']['#rows'][] = [
$this->t('Installation profile'),
[
'data' => [
'#type' => 'operations',
'#links' => $links,
],
],
],
];
];
}
return $build;
}
@@ -488,6 +493,10 @@ class ConfigUpdateController extends ControllerBase {
'data' => $this->t('Type'),
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
],
'provider' => [
'data' => $this->t('Provider'),
'class' => [RESPONSIVE_PRIORITY_LOW],
],
'operations' => [
'data' => $this->t('Operations'),
],
@@ -510,6 +519,7 @@ class ConfigUpdateController extends ControllerBase {
if (!$entity_type) {
// This is simple config.
$id = $name;
$label = '';
$type_label = $this->t('Simple configuration');
$entity_type = 'system.simple';
}
@@ -517,13 +527,57 @@ class ConfigUpdateController extends ControllerBase {
$definition = $this->configList->getType($entity_type);
$id_key = $definition->getKey('id');
$id = $config[$id_key];
// The label key is not required.
if ($label_key = $definition->getKey('label')) {
$label = $config[$label_key];
}
else {
$label = '';
}
$type_label = $definition->getLabel();
}
$label = (isset($config['label'])) ? $config['label'] : '';
$row[] = $name;
$row[] = $label;
$row[] = $type_label;
$provider = $this->configList->getConfigProvider($name);
$provider_name = '';
if (!empty($provider)) {
switch($provider[0]) {
case 'profile':
$provider_name = $this->moduleHandler->getName($provider[1]);
if ($provider_name) {
$provider_name = $this->t('@name profile', ['@name' => $provider_name]);
}
else {
$provider_name = '';
}
break;
case 'module':
$provider_name = $this->moduleHandler->getName($provider[1]);
if ($provider_name) {
$provider_name = $this->t('@name module', ['@name' => $provider_name]);
}
else {
$provider_name = '';
}
break;
case 'theme':
$provider_name = $this->themeHandler->getName($provider[1]);
if ($provider_name) {
$provider_name = $this->t('@name theme', ['@name' => $provider_name]);
}
else {
$provider_name = '';
}
break;
break;
}
}
$row[] = $provider_name;
$links = [];
$routes = [
@@ -33,12 +33,17 @@ class ConfigUpdateTest extends WebTestBase {
parent::setUp();
// Create user and log in.
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer search', 'view config updates report', 'synchronize configuration', 'export configuration', 'import configuration', 'revert configuration', 'delete configuration']);
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer search', 'view config updates report', 'synchronize configuration', 'export configuration', 'import configuration', 'revert configuration', 'delete configuration', 'administer filters']);
$this->drupalLogin($this->adminUser);
// Make sure local tasks and page title are showing.
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
// Load the Drush include file so that its functions can be tested, plus
// the Drush testing include file.
module_load_include('inc', 'config_update_ui', 'config_update_ui.drush_testing');
module_load_include('inc', 'config_update_ui', 'config_update_ui.drush');
}
/**
@@ -50,18 +55,33 @@ class ConfigUpdateTest extends WebTestBase {
$this->clickLink('Updates report');
$this->assertNoReport();
// Verify the Drush list types command.
$output = implode("\n", drush_config_update_ui_config_list_types());
$this->assertTrue(strpos($output, 'search_page') !== FALSE);
$this->assertTrue(strpos($output, 'node_type') !== FALSE);
$this->assertTrue(strpos($output, 'user_role') !== FALSE);
$this->assertTrue(strpos($output, 'block') !== FALSE);
// Verify some empty reports.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
$this->assertDrushReports('type', 'search_page', [], [], [], []);
// Module, theme, and profile reports have no 'added' section.
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->assertReport('Search module', [], [], [], [], ['added']);
$this->assertDrushReports('module', 'search', [], [], [], []);
$this->drupalGet('admin/config/development/configuration/report/theme/classy');
$this->assertReport('Classy theme', [], [], [], [], ['added']);
$this->assertDrushReports('theme', 'classy', [], [], [], []);
$inactive = ['locale.settings' => 'Simple configuration'];
$this->drupalGet('admin/config/development/configuration/report/profile');
$this->assertReport('Testing profile', [], [], [], $inactive, ['added']);
// The locale.settings line should show that the Testing profile is the
// provider.
$this->assertText('Testing profile');
$this->assertDrushReports('profile', '', [], [], [], array_keys($inactive));
// Delete the user search page from the search UI and verify report for
// both the search page config type and user module.
@@ -71,8 +91,15 @@ class ConfigUpdateTest extends WebTestBase {
$inactive = ['search.page.user_search' => 'Users'];
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], $inactive);
// The search.page.user_search line should show that the User module is the
// provider.
$this->assertText('User module');
$this->assertDrushReports('type', 'search_page', [], [], [], array_keys($inactive));
$this->drupalGet('admin/config/development/configuration/report/module/user');
$this->assertReport('User module', [], [], [], $inactive, ['added', 'changed']);
$this->assertDrushReports('module', 'user', [], [], [],
['rdf.mapping.user.user', 'search.page.user_search', 'views.view.user_admin_people', 'views.view.who_s_new', 'views.view.who_s_online' ], ['changed']);
// Use the import link to get it back. Do this from the search page
// report to make sure we are importing the right config.
@@ -82,6 +109,23 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertNoReport();
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
$this->assertDrushReports('type', 'search_page', [], [], [], []);
// Verify that after import, there is no config hash generated.
$this->drupalGet('admin/config/development/configuration/single/export/search_page/user_search');
$this->assertText('id: user_search');
$this->assertNoText('default_config_hash:');
// Test importing again, this time using the Drush import command.
$this->drupalGet('admin/config/search/pages');
$this->clickLink('Delete');
$this->drupalPostForm(NULL, [], 'Delete');
$inactive = ['search.page.user_search' => 'Users'];
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], $inactive);
drush_config_update_ui_config_import_missing('search.page.user_search');
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
// Edit the node search page from the search UI and verify report.
$this->drupalGet('admin/config/search/pages');
@@ -93,6 +137,7 @@ class ConfigUpdateTest extends WebTestBase {
$changed = ['search.page.node_search' => 'New label'];
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], $changed, []);
$this->assertDrushReports('type', 'search_page', [], [], array_keys($changed), []);
// Test the show differences link.
$this->clickLink('Show differences');
@@ -101,6 +146,13 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertText('node');
$this->assertText('new_path');
// Test the show differences Drush command.
$output = drush_config_update_ui_config_diff('search.page.node_search');
$this->assertTrue(strpos($output, 'Content') !== FALSE);
$this->assertTrue(strpos($output, 'New label') !== FALSE);
$this->assertTrue(strpos($output, 'node') !== FALSE);
$this->assertTrue(strpos($output, 'new_path') !== FALSE);
// Test the Back link.
$this->clickLink("Back to 'Updates report' page.");
$this->assertNoReport();
@@ -114,6 +166,14 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertText('path: new_path');
$this->assertText('search.page.node_search.yml');
// Grab the uuid and hash lines for the next test.
$text = $this->getTextContent();
$matches = [];
preg_match('|^.*uuid:.*$|m', $text, $matches);
$uuid_line = $matches[0];
preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
$hash_line = $matches[0];
// Test reverting.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Revert to source');
@@ -125,18 +185,53 @@ class ConfigUpdateTest extends WebTestBase {
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
// Verify that the uuid and hash keys were retained in the revert.
$this->drupalGet('admin/config/development/configuration/single/export/search_page/node_search');
$this->assertText('id: node_search');
$this->assertText($uuid_line);
$this->assertText($hash_line);
// Test reverting again, this time using Drush single revert command.
$this->drupalGet('admin/config/search/pages');
$this->clickLink('Edit');
$this->drupalPostForm(NULL, [
'label' => 'New label',
'path' => 'new_path',
], 'Save search page');
$changed = ['search.page.node_search' => 'New label'];
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], $changed, []);
drush_config_update_ui_config_revert('search.page.node_search');
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
// Test reverting again, this time using Drush multiple revert command.
$this->drupalGet('admin/config/search/pages');
$this->clickLink('Edit');
$this->drupalPostForm(NULL, [
'label' => 'New label',
'path' => 'new_path',
], 'Save search page');
$changed = ['search.page.node_search' => 'New label'];
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], $changed, []);
drush_config_update_ui_config_revert_multiple('type', 'search_page');
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
// Add a new search page from the search UI and verify report.
$this->drupalPostForm('admin/config/search/pages', [
'search_type' => 'node_search',
], 'Add new page');
], 'Add search page');
$this->drupalPostForm(NULL, [
'label' => 'test',
'id' => 'test',
'path' => 'test',
], 'Add search page');
], 'Save');
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$added = ['search.page.test' => 'test'];
$this->assertReport('Search page', [], $added, [], []);
$this->assertDrushReports('type', 'search_page', [], array_keys($added), [], []);
// Test the export link.
$this->clickLink('Export');
@@ -174,13 +269,31 @@ class ConfigUpdateTest extends WebTestBase {
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->clickLink('Export');
$this->assertText('minimum_word_size: 4');
// Grab the hash line for the next test.
$text = $this->getTextContent();
$matches = [];
preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
$hash_line = $matches[0];
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->clickLink('Revert to source');
$this->drupalPostForm(NULL, [], 'Revert');
// Verify that the hash was retained in the revert.
$this->drupalGet('admin/config/development/configuration/single/export/system.simple/search.settings');
$this->assertText($hash_line);
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->assertReport('Search module', [], [], [], [], ['added']);
// Edit the plain_text filter from the filter UI and verify report.
// The filter_format config type uses a label key other than 'label'.
$this->drupalPostForm('admin/config/content/formats/manage/plain_text', [
'name' => 'New label',
], 'Save configuration');
$changed = ['filter.format.plain_text' => 'New label'];
$this->drupalGet('admin/config/development/configuration/report/type/filter_format');
$this->assertReport('Text format', [], [], $changed, []);
}
/**
@@ -193,7 +306,7 @@ class ConfigUpdateTest extends WebTestBase {
* @param string[] $missing
* Array of items that should be listed as missing, name => label.
* @param string[] $added
* Array of items that should be listed as missing, name => label.
* Array of items that should be listed as added, name => label.
* @param string[] $changed
* Array of items that should be listed as changed, name => label.
* @param string[] $inactive
@@ -262,6 +375,66 @@ class ConfigUpdateTest extends WebTestBase {
}
}
/**
* Asserts that the Drush reports have the correct content.
*
* @param string $type
* Type of report to run (type, module, theme, etc.).
* @param string $name
* Name of that type to run (e.g., module machine name).
* @param string[] $missing
* Array of config items that should be listed as missing.
* @param string[] $added
* Array of config items that should be listed as added.
* @param string[] $changed
* Array of config items that should be listed as changed.
* @param string[] $inactive
* Array of config items that should be listed as inactive.
* @param string[] $skip
* Array of report sections to skip checking.
*/
protected function assertDrushReports($type, $name, $missing, $added, $changed, $inactive, $skip = []) {
if (!in_array('missing', $skip)) {
$output = drush_config_update_ui_config_missing_report($type, $name);
$this->assertEqual(count($output), count($missing), 'Drush missing report has correct number of items');
if (count($missing)) {
foreach ($missing as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush missing report");
}
}
}
if (!in_array('added', $skip) && $type == 'type') {
$output = drush_config_update_ui_config_added_report($name);
$this->assertEqual(count($output), count($added), 'Drush added report has correct number of items');
if (count($added)) {
foreach ($added as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush added report");
}
}
}
if (!in_array('changed', $skip)) {
$output = drush_config_update_ui_config_different_report($type, $name);
$this->assertEqual(count($output), count($changed), 'Drush changed report has correct number of items');
if (count($changed)) {
foreach ($changed as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush changed report");
}
}
}
if (!in_array('inactive', $skip)) {
$output = drush_config_update_ui_config_inactive_report($type, $name);
$this->assertEqual(count($output), count($inactive), 'Drush inactive report has correct number of items');
if (count($inactive)) {
foreach ($inactive as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush inactive report");
}
}
}
}
/**
* Asserts that the report is not shown.
*
@@ -278,6 +451,27 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertNoText('Added configuration items');
$this->assertNoText('Changed configuration items');
$this->assertNoText('Unchanged configuration items');
// Verify that certain report links are shown or not shown. For extensions,
// only extensions that have configuration should be shown.
// Modules.
$this->assertLink('Search');
$this->assertLink('Field');
$this->assertNoLink('Configuration Update Base');
$this->assertNoLink('Configuration Update Reports');
// Themes.
$this->assertNoLink('Stark');
$this->assertNoLink('Classy');
// Profiles.
$this->assertLink('Testing');
// Configuration types.
$this->assertLink('All types');
$this->assertLink('Simple configuration');
$this->assertLink('Search page');
}
}
@@ -76,6 +76,10 @@ class ConfigDiffer implements ConfigDiffInterface {
* @see ConfigDiffer::$ignore
*/
protected function normalize($config) {
if (empty($config)) {
return [];
}
// Remove "ignore" elements.
foreach ($this->ignore as $element) {
unset($config[$element]);
@@ -0,0 +1,37 @@
<?php
namespace Drupal\config_update;
/**
* Defines an extended interface for listing configuration.
*/
interface ConfigListByProviderInterface extends ConfigListInterface {
/**
* Returns the provider of a given config object.
*
* @param string $name
* Name of the config object.
*
* @return string[]
* Array containing the type of extension providing this config object as
* its first element (module, theme, profile), and the name of the provider
* as its second element. NULL if unknown.
*/
public function getConfigProvider($name);
/**
* Lists the providers of a given type that actually have configuration.
*
* @param string $type
* Type of extension (module, theme, profile).
* @param string $name
* Machine name of extension.
*
* @return bool
* TRUE if the extension has either install or optional config, and FALSE
* if it does not.
*/
public function providerHasConfig($type, $name);
}
@@ -38,13 +38,13 @@ interface ConfigListInterface {
public function getTypeByPrefix($prefix);
/**
* Returns the entity type object for a given config object.
* Returns the config type name for a given config object.
*
* @param string $name
* Name of the config object.
*
* @return string
* Name of the entity type that this is an instance of, determined by
* Name of the config type that this is an instance of, determined by
* prefix. NULL for simple configuration.
*/
public function getTypeNameByConfigName($name);
@@ -82,10 +82,12 @@ class ConfigLister implements ConfigListInterface {
* Sets up and returns the entity definitions list.
*/
public function listTypes() {
// Return list if it has already been calculated.
if (count($this->definitions)) {
return $this->definitions;
}
// Calculate and return the list.
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$this->definitions[$entity_type] = $definition;
@@ -0,0 +1,152 @@
<?php
namespace Drupal\config_update;
use Drupal\Core\Config\ExtensionInstallStorage;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Extension\Extension;
/**
* Provides methods related to config listing, including provider calculation.
*/
class ConfigListerWithProviders extends ConfigLister implements ConfigListByProviderInterface {
/**
* List of providers of config, keyed by config item name.
*
* The array elements are each arrays, with the type of extension providing
* the config object as the first element (module, theme, profile), and the
* name of the provider as the second element.
*
* This is not set up until ConfigListerWithProviders::listProviders() has
* been called.
*
* @var array
*/
protected $providers = [];
/**
* List of extensions that provide configuration, keyed by type.
*
* This is not set up until ConfigListerWithProviders::listProviders() has
* been called.
*
* @var array
*/
protected $extensionsWithConfig = [];
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Constructs a ConfigListerWithProviders.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Config\StorageInterface $active_config_storage
* The active config storage.
* @param \Drupal\Core\Config\ExtensionInstallStorage $extension_config_storage
* The extension config storage.
* @param \Drupal\Core\Config\ExtensionInstallStorage $extension_optional_config_storage
* The extension config storage for optional config items.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(EntityTypeManagerInterface $entity_manager, StorageInterface $active_config_storage, ExtensionInstallStorage $extension_config_storage, ExtensionInstallStorage $extension_optional_config_storage, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
$this->entityManager = $entity_manager;
$this->activeConfigStorage = $active_config_storage;
$this->extensionConfigStorage = $extension_config_storage;
$this->extensionOptionalConfigStorage = $extension_optional_config_storage;
$this->moduleHandler = $module_handler;
$this->themeHandler = $theme_handler;
}
/**
* Sets up and returns the config providers list.
*/
public function listProviders() {
// Return the list if it has already been set up.
if (count($this->providers)) {
return $this->providers;
}
// Calculate if it hasn't been set up yet.
// List all of the profile, modules, and themes.
$extensionsToDo = [];
$profile = Settings::get('install_profile');
$extensionsToDo[] = ['profile', $profile];
$modules = $this->moduleHandler->getModuleList();
foreach ($modules as $machine_name => $module) {
if ($machine_name != $profile) {
$extensionsToDo[] = ['module', $machine_name];
}
}
$themes = $this->themeHandler->listInfo();
foreach ($themes as $machine_name => $theme) {
$extensionsToDo[] = ['theme', $machine_name];
}
// For each extension, figure out if it has config, and make an index of
// config item => provider.
$this->extensionsWithConfig = [
'profile' => [],
'module' => [],
'theme' => [],
];
foreach($extensionsToDo as $item) {
$type = $item[0];
$name = $item[1];
$configs = array_merge($this->listProvidedItems($type, $name),
$this->listProvidedItems($type, $name, TRUE));
if (!empty($configs)) {
$this->extensionsWithConfig[$type][$name] = $name;
foreach($configs as $config) {
$this->providers[$config] = [$type, $name];
}
}
}
return $this->providers;
}
/**
* {@inheritdoc}
*/
public function getConfigProvider($name) {
$providers = $this->listProviders();
return (isset($providers[$name])) ? $providers[$name] : NULL;
}
/**
* {@inheritdoc}
*/
public function providerHasConfig($type, $name) {
// Make sure the list of extensions with configuration has been generated.
$this->listProviders();
return isset($this->extensionsWithConfig[$type][$name]);
}
}
@@ -129,21 +129,25 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
}
if ($type == 'system.simple') {
// Load the current config and replace the value.
$this->configFactory->getEditable($full_name)->setData($value)->save();
// Load the current config and replace the value, retaining the config
// hash (which is part of the _core config key's value).
$config = $this->configFactory->getEditable($full_name);
$core = $config->get('_core');
$config->setData($value);
$config->set('_core', $core);
$config->save();
}
else {
// Load the current config entity and replace the value, with the
// old UUID.
// Load the current config entity and replace the value. Note that
// the uuid and _core/hash values are retained for entity-based config,
// since updateFromStorageRecord() only updates values that are part of
// the passed-in array, and doesn't remove or alter other values.
$definition = $this->entityManager->getDefinition($type);
$id_key = $definition->getKey('id');
$id = $value[$id_key];
$entity_storage = $this->entityManager->getStorage($type);
$entity = $entity_storage->load($id);
$uuid = $entity->get('uuid');
$entity = $entity_storage->updateFromStorageRecord($entity, $value);
$entity->set('uuid', $uuid);
$entity->save();
}
@@ -5,6 +5,12 @@ name: Default
machine_name: default
description: ''
assignments:
alter:
core: true
uuid: true
user_permissions: true
enabled: true
weight: 0
base:
types:
config:
@@ -267,11 +267,14 @@ function drush_features_list_packages($package_name = '') {
*
*/
function drush_features_import_all() {
_drush_features_options();
$assigner = _drush_features_options();
$current_bundle = $assigner->getBundle();
$namespace = $current_bundle->isDefault() ? '' : $current_bundle->getMachineName();
/** @var \Drupal\features\FeaturesManagerInterface $manager */
$manager = \Drupal::service('features.manager');
$packages = $manager->getPackages();
$packages = $manager->filterPackages($packages);
$packages = $manager->filterPackages($packages, $namespace);
$overridden = array();
foreach ($packages as $package) {
@@ -491,7 +494,7 @@ function drush_features_diff() {
$filter_ctypes = explode(',', $filter_ctypes);
}
$feature = _drush_features_load_feature($module, TRUE);
$feature = $manager->loadPackage($module, TRUE);
if (empty($feature)) {
drush_log(dt('No such feature is available: @module', array('@module' => $module)), 'error');
return;
@@ -583,8 +586,6 @@ function drush_features_import() {
/** @var \Drupal\features\FeaturesManagerInterface $manager */
$manager = \Drupal::service('features.manager');
/** @var \Drupal\config_update\ConfigRevertInterface $config_revert */
$config_revert = \Drupal::service('features.config_update');
// Parse list of arguments.
$modules = array();
@@ -613,7 +614,7 @@ function drush_features_import() {
$dt_args['@module'] = $module;
/** @var \Drupal\features\Package $feature */
$feature = _drush_features_load_feature($module, TRUE);
$feature = $manager->loadPackage($module, TRUE);
if (empty($feature)) {
drush_log(dt('No such feature is available: @module', $dt_args), 'error');
return;
@@ -644,30 +645,33 @@ function drush_features_import() {
drush_log(dt('Current state already matches active config, aborting.'), 'ok');
}
else {
$config = $manager->getConfigCollection();
// Determine which config the user wants to import/revert.
$config_to_create = [];
foreach ($components as $component) {
$dt_args['@component'] = $component;
$confirmation_message = 'Do you really want to import @module : @component?';
if ($skip_confirmation || drush_confirm(dt($confirmation_message, $dt_args))) {
if (!isset($config[$component])) {
// Import missing component.
/** @var array $item */
$item = $manager->getConfigType($component);
$type = ConfigurationItem::fromConfigStringToConfigType($item['type']);
$config_revert->import($type, $item['name_short']);
drush_log(dt('Import @module : @component.', $dt_args), 'ok');
}
else {
// Revert existing component.
/** @var \Drupal\features\ConfigurationItem $item */
$item = $config[$component];
$type = ConfigurationItem::fromConfigStringToConfigType($item->getType());
$config_revert->revert($type, $item->getShortName());
drush_log(dt('Reverted @module : @component.', $dt_args), 'ok');
}
$config_to_create[$component] = '';
}
}
// Perform the import/revert.
$config_imported = $manager->createConfiguration($config_to_create);
// List the results.
foreach ($components as $component) {
$dt_args['@component'] = $component;
if (isset($config_imported['new'][$component])) {
drush_log(dt('Imported @module : @component.', $dt_args), 'ok');
}
elseif (isset($config_imported['updated'][$component])) {
drush_log(dt('Reverted @module : @component.', $dt_args), 'ok');
}
elseif (!isset($config_to_create[$component])) {
drush_log(dt('Skipping @module : @component.', $dt_args), 'ok');
}
else {
drush_log(dt('Skipping @module : @component.', $dt_args), 'ok');
drush_log(dt('Error importing @module : @component.', $dt_args), 'error');
}
}
}
@@ -679,35 +683,6 @@ function drush_features_import() {
}
}
/**
* Loads a Features package.
*
* @param string $module
* The machine name of a module.
* @param bool $any
* If TRUE then check for any module, not just a Features module.
*
* @return array
*/
function _drush_features_load_feature($module, $any = FALSE) {
/** @var \Drupal\features\FeaturesManagerInterface $manager */
$manager = \Drupal::service('features.manager');
$feature = $manager->getPackage($module);
if ($any && !isset($feature)) {
// See if this is a non-features module.
$module_handler = \Drupal::moduleHandler();
$modules = $module_handler->getModuleList();
if (!empty($modules[$module])) {
$extension = $modules[$module];
$feature = $manager->initPackageFromExtension($extension);
$config = $manager->listExtensionConfig($extension);
$feature->setConfig($config);
$feature->setStatus(FeaturesManagerInterface::STATUS_INSTALLED);
}
}
return $feature;
}
/**
* Returns an array of full config names given a array[$type][$component].
*
@@ -7,8 +7,8 @@ dependencies:
- config
- config_update
# Information added by Drupal.org packaging script on 2016-09-02
version: '8.x-3.0-beta8'
# Information added by Drupal.org packaging script on 2017-03-07
version: '8.x-3.5'
core: '8.x'
project: 'features'
datestamp: 1472847281
datestamp: 1488908587
@@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains install and update functions for Features.
*/
/**
* Rebuild the container to add a parameter to the features.manager service.
*/
function features_update_8300() {
// Empty update to cause a cache rebuild so that the container is rebuilt.
}
/**
* Update existing feature bundles with new alter plugin configuration.
*/
function features_update_8301() {
foreach (\Drupal::service('entity_type.manager')->getStorage('features_bundle')->loadMultiple() as $bundle) {
$bundle = \Drupal::configFactory()->getEditable('features.bundle.' . $bundle->id());
$assignments = $bundle->get('assignments');
$assignments['alter'] = [
'core' => TRUE,
'uuid' => TRUE,
'user_permissions' => TRUE,
'enabled' => TRUE,
'weight' => 0,
];
$bundle->set('assignments', $assignments)->save();
}
}
@@ -28,10 +28,19 @@ function features_help($route_name, RouteMatchInterface $route_match) {
function features_file_download($uri) {
$scheme = file_uri_scheme($uri);
$target = file_uri_target($uri);
if ($scheme == 'temporary' && $target) {
return array(
'Content-disposition' => 'attachment; filename="' . $target . '"',
);
$request = \Drupal::request();
$route = $request->attributes->get('_route');
// Check if we were called by Features download route.
// No additional access checking needed here: route requires
// "export configuration" permission, token is validated by the controller.
// @see \Drupal\features\Controller\FeaturesController::downloadExport()
if ($route == 'features.export_download') {
return array(
'Content-disposition' => 'attachment; filename="' . $target . '"',
);
}
}
}
@@ -17,7 +17,7 @@ services:
- [initFeaturesManager]
features.manager:
class: Drupal\features\FeaturesManager
arguments: ['@app.root', '@entity.manager', '@config.factory', '@config.storage', '@config.manager', '@module_handler']
arguments: ['@app.root', '@entity.manager', '@config.factory', '@config.storage', '@config.manager', '@module_handler', '@features.config_update']
features.config_update:
class: Drupal\config_update\ConfigReverter
@@ -30,3 +30,9 @@ services:
features.extension_optional_storage:
class: Drupal\features\FeaturesInstallStorage
arguments: ['@config.storage', 'config/optional']
features.config.installer:
class: Drupal\features\FeaturesConfigInstaller
decorates: config.installer
decoration_priority: 9
arguments: ['@features.config.installer.inner', '@features.manager', '@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher']
@@ -36,6 +36,16 @@ span.features-item-list span {
white-space: nowrap;
}
.features-listing span.features-moved,
.features-listing a.features-moved {
color: #fff;
background-color: #215900 !important;
border-radius: 5px;
margin-right: 5px;
padding: 2px 5px;
white-space: nowrap;
}
.features-listing span.features-missing,
.features-listing a.features-missing {
color: #fff;
@@ -269,3 +279,8 @@ details.features-export-component .details-wrapper {
display: inline-block;
font-size: 10px;
}
/** Styles for plugin config forms **/
.features-assignment-settings-form .fieldset-wrapper {
padding-left: 16px;
}
@@ -7,8 +7,8 @@ configure: features.assignment
dependencies:
- features
# Information added by Drupal.org packaging script on 2016-09-02
version: '8.x-3.0-beta8'
# Information added by Drupal.org packaging script on 2017-03-07
version: '8.x-3.5'
core: '8.x'
project: 'features'
datestamp: 1472847281
datestamp: 1488908587
@@ -15,6 +15,15 @@ features.assignment:
requirements:
_permission: 'administer site configuration'
features.assignment_alter:
path: '/admin/config/development/features/bundle/_alter/{bundle_name}'
defaults:
_form: '\Drupal\features_ui\Form\AssignmentAlterForm'
_title: 'Configure package configuration altering'
bundle_name: NULL
requirements:
_permission: 'administer site configuration'
features.assignment_base:
path: '/admin/config/development/features/bundle/_base/{bundle_name}'
defaults:
@@ -0,0 +1,76 @@
<?php
namespace Drupal\features_ui\Form;
use Drupal\Core\Form\FormStateInterface;
/**
* Configures the selected configuration assignment method for this site.
*/
class AssignmentAlterForm extends AssignmentFormBase {
const METHOD_ID = 'alter';
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'features_assignment_alter_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
$core_setting = $settings['core'];
$uuid_setting = $settings['uuid'];
$user_permissions_setting = $settings['user_permissions'];
$form['core'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Strip out <em>_core</em> property.'),
'#default_value' => $core_setting,
'#description' => $this->t('Select this option to remove the <em>_core</em> configuration property on export. This property is added by Drupal core when configuration is installed.'),
);
$form['uuid'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Strip out <em>uuid</em> property.'),
'#default_value' => $uuid_setting,
'#description' => $this->t('Select this option to remove the <em>uuid</em> configuration property on export. This property is added by Drupal core when configuration is installed.'),
);
$form['user_permissions'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Strip out user permissions.'),
'#default_value' => $user_permissions_setting,
'#description' => $this->t('Select this option to remove permissions from user roles on export.'),
);
$this->setActions($form, self::METHOD_ID);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Merge in selections.
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
$settings = array_merge($settings, [
'core' => $form_state->getValue('core'),
'uuid' => $form_state->getValue('uuid'),
'user_permissions' => $form_state->getValue('user_permissions'),
]);
$this->currentBundle->setAssignmentSettings(self::METHOD_ID, $settings)->save();
$this->setRedirect($form_state);
drupal_set_message($this->t('Package assignment configuration saved.'));
}
}
@@ -33,7 +33,7 @@ class AssignmentBaseForm extends AssignmentFormBase {
// the config type select options.
$this->setContentTypeSelect($form, $settings['types']['content'], $this->t('base'), TRUE);
$this->setActions($form);
$this->setActions($form, self::METHOD_ID);
return $form;
}
@@ -408,7 +408,7 @@ class AssignmentConfigureForm extends FormBase {
}
// Otherwise, load the current bundle and rename if needed.
else {
$bundle = $this->assigner->getBundle();
$bundle = $this->assigner->loadBundle();
$old_name = $bundle->getMachineName();
$new_name = $form_state->getValue(array('bundle', 'machine_name'));
if ($old_name != $new_name) {
@@ -26,7 +26,7 @@ class AssignmentCoreForm extends AssignmentFormBase {
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('core'));
$this->setActions($form);
$this->setActions($form, self::METHOD_ID);
return $form;
}
@@ -25,27 +25,30 @@ class AssignmentExcludeForm extends AssignmentFormBase {
$this->currentBundle = $this->assigner->loadBundle($bundle_name);
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('exclude'));
$module_settings = $settings['module'];
$curated_settings = $settings['curated'];
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('exclude'), FALSE,
$this->t("Select types of configuration that should be excluded from packaging."));
$form['curated'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Exclude designated site-specific configuration'),
'#default_value' => $curated_settings,
'#description' => $this->t('Select this option to exclude from packaging items on a curated list of site-specific configuration.'),
'#description' => $this->t('Select this option to exclude a curated list of site-specific configuration from packaging.'),
);
$form['module'] = array(
'#type' => 'container',
'#type' => 'fieldset',
'#tree' => TRUE,
'#title' => $this->t('Exclude configuration provided by modules'),
);
$form['module']['installed'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Exclude installed module-provided entity configuration'),
'#default_value' => $module_settings['installed'],
'#description' => $this->t('Select this option to exclude from packaging any configuration that is provided by already installed modules.'),
'#description' => $this->t('Select this option to exclude configuration provided by INSTALLED modules from reassignment.'),
'#attributes' => array(
'data-module-installed' => 'status',
),
@@ -62,17 +65,17 @@ class AssignmentExcludeForm extends AssignmentFormBase {
'#type' => 'checkbox',
'#title' => $this->t("Don't exclude install profile's configuration"),
'#default_value' => $module_settings['profile'],
'#description' => $this->t("Select this option to not exclude from packaging any configuration that is provided by this site's install profile, %profile.", array('%profile' => $info['name'])),
'#description' => $this->t("Select this option to allow configuration provided by the site's install profile (%profile) to be reassigned.", array('%profile' => $info['name'])),
'#states' => $show_if_module_installed_checked,
);
$machine_name = $this->currentBundle->getMachineName();
$machine_name = !empty($machine_name) ? $machine_name : $this->t('none');
$bundle_name = $this->currentBundle->getMachineName();
$bundle_name = !empty($bundle_name) ? $bundle_name : $this->t('none');
$form['module']['namespace'] = array(
'#type' => 'checkbox',
'#title' => $this->t("Don't exclude non-installed configuration by namespace"),
'#default_value' => $module_settings['namespace'],
'#description' => $this->t("Select this option to not exclude from packaging any configuration that is provided by non-installed modules with the package namespace (currently %namespace).", array('%namespace' => $machine_name)),
'#description' => $this->t("Select this option to allow configuration provided by uninstalled modules with the bundle namespace (%namespace_*) to be reassigned.", array('%namespace' => $bundle_name)),
'#states' => $show_if_module_installed_checked,
'#attributes' => array(
'data-namespace' => 'status',
@@ -90,12 +93,12 @@ class AssignmentExcludeForm extends AssignmentFormBase {
'#type' => 'checkbox',
'#title' => $this->t("Don't exclude ANY configuration by namespace"),
'#default_value' => $module_settings['namespace_any'],
'#description' => $this->t("Select this option to not exclude from packaging any configuration that is provided by ANY modules with the package namespace (currently %namespace).
Warning: Can cause installed configuration to be reassigned to different packages.", array('%namespace' => $machine_name)),
'#description' => $this->t("Select this option to allow configuration provided by ANY modules with the bundle namespace (%namespace_*) to be reassigned.
Warning: Can cause installed configuration to be reassigned to different packages.", array('%namespace' => $bundle_name)),
'#states' => $show_if_namespace_checked,
);
$this->setActions($form);
$this->setActions($form, self::METHOD_ID);
return $form;
}
@@ -5,7 +5,7 @@ namespace Drupal\features_ui\Form;
use Drupal\features\FeaturesManagerInterface;
use Drupal\features\FeaturesAssignerInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -30,11 +30,11 @@ abstract class AssignmentFormBase extends FormBase {
protected $assigner;
/**
* The entity manager.
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityManager;
protected $entityTypeManager;
/**
* The current bundle.
@@ -50,13 +50,13 @@ abstract class AssignmentFormBase extends FormBase {
* The features manager.
* @param \Drupal\features\FeaturesAssignerInterface $assigner
* The assigner.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner, EntityManagerInterface $entity_manager) {
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner, EntityTypeManagerInterface $entity_type_manager) {
$this->featuresManager = $features_manager;
$this->assigner = $assigner;
$this->entityManager = $entity_manager;
$this->entityTypeManager = $entity_type_manager;
}
/**
@@ -66,14 +66,14 @@ abstract class AssignmentFormBase extends FormBase {
return new static(
$container->get('features.manager'),
$container->get('features_assigner'),
$container->get('entity.manager')
$container->get('entity_type.manager')
);
}
/**
* Adds configuration types checkboxes.
*/
protected function setConfigTypeSelect(&$form, $defaults, $type, $bundles_only = FALSE) {
protected function setConfigTypeSelect(&$form, $defaults, $type, $bundles_only = FALSE, $description = '') {
$options = $this->featuresManager->listConfigTypes($bundles_only);
if (!isset($form['types'])) {
@@ -86,7 +86,7 @@ abstract class AssignmentFormBase extends FormBase {
$form['types']['config'] = array(
'#type' => 'checkboxes',
'#title' => $this->t('Configuration types'),
'#description' => $this->t('Select types of configuration that should be considered @type types.', array('@type' => $type)),
'#description' => !empty($description) ? $description : $this->t('Select types of configuration that should be considered @type types.', array('@type' => $type)),
'#options' => $options,
'#default_value' => $defaults,
);
@@ -96,7 +96,7 @@ abstract class AssignmentFormBase extends FormBase {
* Adds content entity types checkboxes.
*/
protected function setContentTypeSelect(&$form, $defaults, $type, $exclude_has_config_bundles = TRUE) {
$entity_types = $this->entityManager->getDefinitions();
$entity_types = $this->entityTypeManager->getDefinitions();
$has_config_bundle = array();
foreach ($entity_types as $definition) {
@@ -138,13 +138,29 @@ abstract class AssignmentFormBase extends FormBase {
/**
* Adds a "Save settings" submit action.
*/
protected function setActions(&$form) {
protected function setActions(&$form, $method_id = NULL) {
$assignment_info = $this->assigner->getAssignmentMethods();
if (isset($method_id) && isset($assignment_info[$method_id])) {
$method = $assignment_info[$method_id];
$form['help_text'] = array(
'#markup' => $method['description'],
'#prefix' => '<p class="messages messages--status">',
'#suffix' => '</p>',
'#weight' => -99,
);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#button_type' => 'primary',
'#value' => $this->t('Save settings'),
);
$form['#attributes']['class'][] = 'features-assignment-settings-form';
$form['#attached'] = array(
'library' => array(
'features_ui/drupal.features_ui.admin',
));
}
/**
@@ -26,7 +26,7 @@ class AssignmentOptionalForm extends AssignmentFormBase {
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('optional'));
$this->setActions($form);
$this->setActions($form, self::METHOD_ID);
return $form;
}
@@ -54,7 +54,7 @@ class AssignmentProfileForm extends AssignmentFormBase {
'#description' => $this->t('Select this option to add module and theme dependencies from the Standard install profile.'),
);
$this->setActions($form);
$this->setActions($form, self::METHOD_ID);
return $form;
}
@@ -26,7 +26,7 @@ class AssignmentSiteForm extends AssignmentFormBase {
$settings = $this->currentBundle->getAssignmentSettings(self::METHOD_ID);
$this->setConfigTypeSelect($form, $settings['types']['config'], $this->t('site'));
$this->setActions($form);
$this->setActions($form, self::METHOD_ID);
return $form;
}
@@ -191,6 +191,10 @@ class FeaturesEditForm extends FormBase {
// Make sure the current bundle matches what is stored in the package.
// But only do this if the Package value hasn't been manually changed.
$bundle = $this->assigner->getBundle($this->package->getBundle());
if (empty($bundle)) {
// Create bundle if it doesn't exist yet
$bundle = $this->assigner->createBundleFromDefault($this->package->getBundle());
}
$this->bundle = $bundle->getMachineName();
$this->assigner->reset();
$this->assigner->assignConfigPackages(TRUE);
@@ -398,8 +402,11 @@ class FeaturesEditForm extends FormBase {
* @return bool
*/
public function featureExists($value, $element, $form_state) {
$bundle = $this->assigner->getBundle($this->bundle);
$value = $bundle->getFullName($value);
$packages = $this->featuresManager->getPackages();
return isset($packages[$value]) || \Drupal::moduleHandler()->moduleExists($value);
// A package may conflict only if it's been exported.
return (isset($packages[$value]) && ($packages[$value]->getState() !== FeaturesManagerInterface::STATUS_NO_EXPORT)) || \Drupal::moduleHandler()->moduleExists($value);
}
/**
@@ -7,6 +7,7 @@ use Drupal\Component\Utility\Xss;
use Drupal\features\FeaturesAssignerInterface;
use Drupal\features\FeaturesGeneratorInterface;
use Drupal\features\FeaturesManagerInterface;
use Drupal\features\FeaturesBundleInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
@@ -171,7 +172,7 @@ class FeaturesExportForm extends FormBase {
),
);
$form['preview'] = $this->buildListing($packages);
$form['preview'] = $this->buildListing($packages, $current_bundle);
$form['#attached'] = array(
'library' => array(
@@ -224,11 +225,13 @@ class FeaturesExportForm extends FormBase {
*
* @param \Drupal\features\Package[] $packages
* The packages.
* @param \Drupal\features\FeaturesBundleInterface $bundle
* The current bundle
*
* @return array
* A render array of a form element.
*/
protected function buildListing(array $packages) {
protected function buildListing(array $packages, FeaturesBundleInterface $bundle) {
$header = array(
'name' => array('data' => $this->t('Feature')),
@@ -245,7 +248,7 @@ class FeaturesExportForm extends FormBase {
if ($first && $package->getStatus() == FeaturesManagerInterface::STATUS_NO_EXPORT) {
// Don't offer new non-profile packages that are empty.
if ($package->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT &&
!$this->assigner->getBundle()->isProfilePackage($package->getMachineName()) &&
!$bundle->isProfilePackage($package->getMachineName()) &&
empty($package->getConfig())) {
continue;
}
@@ -258,7 +261,7 @@ class FeaturesExportForm extends FormBase {
),
);
}
$options[$package->getMachineName()] = $this->buildPackageDetail($package);
$options[$package->getMachineName()] = $this->buildPackageDetail($package, $bundle);
}
$element = array(
@@ -278,11 +281,13 @@ class FeaturesExportForm extends FormBase {
*
* @param \Drupal\features\Package $package
* The package.
* @param \Drupal\features\FeaturesBundleInterface
* The current bundle.
*
* @return array
* A render array of a form element.
*/
protected function buildPackageDetail(Package $package) {
protected function buildPackageDetail(Package $package, FeaturesBundleInterface $bundle) {
$config_collection = $this->featuresManager->getConfigCollection();
$url = Url::fromRoute('features.edit', array('featurename' => $package->getMachineName()));
@@ -295,7 +300,7 @@ class FeaturesExportForm extends FormBase {
// Except for the 'unpackaged' pseudo-package, display the full name, since
// that's what will be generated.
if ($machine_name !== 'unpackaged') {
$machine_name = $package->getFullName($machine_name);
$machine_name = $bundle->getFullName($machine_name);
}
$element['machine_name'] = $machine_name;
$element['status'] = array(
@@ -312,6 +317,7 @@ class FeaturesExportForm extends FormBase {
$new_config = $this->featuresManager->detectNew($package);
$conflicts = array();
$missing = array();
$moved = array();
if ($package->getStatus() == FeaturesManagerInterface::STATUS_NO_EXPORT) {
$overrides = array();
@@ -342,13 +348,24 @@ class FeaturesExportForm extends FormBase {
}
elseif (!in_array($item_name, $package->getConfig())) {
$item = $config_collection[$item_name];
$conflicts[] = $item_name;
$package_name = !empty($item->getPackage()) ? $item->getPackage() : $this->t('PACKAGE NOT ASSIGNED');
$package_config[$item->getType()][] = array(
'name' => Html::escape($package_name),
'label' => Html::escape($item->getLabel()),
'class' => 'features-conflict',
);
if (empty($item->getProvider())) {
$conflicts[] = $item_name;
$package_name = !empty($item->getPackage()) ? $item->getPackage() : $this->t('PACKAGE NOT ASSIGNED');
$package_config[$item->getType()][] = array(
'name' => Html::escape($package_name),
'label' => Html::escape($item->getLabel()),
'class' => 'features-conflict',
);
}
else {
$moved[] = $item_name;
$package_name = !empty($item->getPackage()) ? $item->getPackage() : $this->t('PACKAGE NOT ASSIGNED');
$package_config[$item->getType()][] = array(
'name' => $this->t('Moved to @package', array('@package' => $package_name)),
'label' => Html::escape($item->getLabel()),
'class' => 'features-moved',
);
}
}
}
// Add dependencies.
@@ -395,6 +412,14 @@ class FeaturesExportForm extends FormBase {
'#attributes' => array('class' => array('features-missing')),
);
}
if (!empty($moved)) {
$state_links[] = array(
'#type' => 'link',
'#title' => $this->t('Moved'),
'#url' => Url::fromRoute('features.edit', array('featurename' => $package->getMachineName())),
'#attributes' => array('class' => array('features-moved')),
);
}
if (!empty($state_links)) {
$element['state'] = array(
'data' => $state_links,
@@ -29,6 +29,10 @@ class FeaturesCreateUITest extends WebTestBase {
* Tests creating a feature via UI and download it.
*/
public function testCreateFeaturesUI() {
list($major, $minor, ) = explode('.', \Drupal::VERSION);
// In D8.3 the module category was removed from the module name field.
$name_prefix = (intval($major) == 8 && intval($minor) > 2) ? 'modules[' : 'modules[Other][';
$feature_name = 'test_feature2';
$admin_user = $this->createUser(['administer site configuration', 'export configuration', 'administer modules']);
$this->drupalLogin($admin_user);
@@ -111,7 +115,7 @@ class FeaturesCreateUITest extends WebTestBase {
$this->drupalGet('admin/modules');
$edit = [
'modules[Other][' . $feature_name . '][enable]' => TRUE,
$name_prefix . $feature_name . '][enable]' => TRUE,
];
$this->drupalPostForm(NULL, $edit, $this->t('Install'));
@@ -143,12 +147,13 @@ class FeaturesCreateUITest extends WebTestBase {
$this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
$this->clickLink($this->t('Changed'));
$this->drupalGet('admin/config/development/features/diff/' . $feature_name);
$this->assertRaw('<td class="diff-context diff-deletedline">anonymous : Anonymous <span class="diffchange">giraffe</span></td>');
$this->assertRaw('<td class="diff-context diff-addedline">anonymous : Anonymous</td>');
$this->drupalGet('admin/modules');
$edit = [
'modules[Other][' . $feature_name . '][enable]' => TRUE,
$name_prefix . $feature_name . '][enable]' => TRUE,
];
$this->drupalPostForm(NULL, $edit, $this->t('Install'));
$this->drupalGet('admin/config/development/features');
@@ -3,8 +3,8 @@
namespace Drupal\features\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\features\FeaturesAssignmentMethodInterface;
use Drupal\features\FeaturesBundleInterface;
use Drupal\Core\Site\Settings;
/**
* Defines a features bundle.
@@ -63,7 +63,7 @@ class FeaturesBundle extends ConfigEntityBase implements FeaturesBundleInterface
/**
* @var bool
*/
protected $is_profile;
protected $is_profile = FALSE;
public function id() {
// @todo Convert it to $this->id in the long run.
@@ -109,7 +109,11 @@ class FeaturesBundle extends ConfigEntityBase implements FeaturesBundleInterface
* {@inheritdoc}
*/
public function getFullName($short_name) {
if ($this->isDefault() || $this->inBundle($short_name)) {
if ($this->isDefault() ||
// If it's already prefixed, don't repeat the prefix.
$this->inBundle($short_name) ||
// If we are a profile, don't duplicate the bundle if same as profile.
$this->isProfilePackage($short_name)) {
return $short_name;
}
else {
@@ -174,7 +178,8 @@ class FeaturesBundle extends ConfigEntityBase implements FeaturesBundleInterface
*/
public function getProfileName() {
$name = $this->isProfile() ? $this->profile_name : '';
return !empty($name) ? $name : drupal_get_profile();
// Use Settings::get to fetch current profile name so we can easily test.
return !empty($name) ? $name : Settings::get('install_profile');
}
/**
@@ -5,7 +5,7 @@ namespace Drupal\features;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ExtensionInstallStorage;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\features\Entity\FeaturesBundle;
@@ -45,11 +45,11 @@ class FeaturesAssigner implements FeaturesAssignerInterface {
protected $configStorage;
/**
* The entity manager.
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityManager;
protected $entityTypeManager;
/**
* Local cache for package assignment method instances.
@@ -79,17 +79,17 @@ class FeaturesAssigner implements FeaturesAssignerInterface {
* The features manager.
* @param \Drupal\Component\Plugin\PluginManagerInterface $assigner_manager
* The package assignment methods plugin manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Config\StorageInterface $config_storage
* The configuration factory.
*/
public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $assigner_manager, EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage) {
public function __construct(FeaturesManagerInterface $features_manager, PluginManagerInterface $assigner_manager, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, StorageInterface $config_storage) {
$this->featuresManager = $features_manager;
$this->assignerManager = $assigner_manager;
$this->entityManager = $entity_manager;
$this->entityTypeManager = $entity_type_manager;
$this->configFactory = $config_factory;
$this->configStorage = $config_storage;
$this->bundles = $this->getBundleList();
@@ -184,7 +184,7 @@ class FeaturesAssigner implements FeaturesAssignerInterface {
$instance = $this->assignerManager->createInstance($method_id, array());
$instance->setFeaturesManager($this->featuresManager);
$instance->setAssigner($this);
$instance->setEntityManager($this->entityManager);
$instance->setEntityTypeManager($this->entityTypeManager);
$instance->setConfigFactory($this->configFactory);
$this->methods[$method_id] = $instance;
}
@@ -262,7 +262,7 @@ class FeaturesAssigner implements FeaturesAssignerInterface {
public function getBundleList() {
if (empty($this->bundles)) {
$this->bundles = array();
foreach ($this->entityManager->getStorage('features_bundle')->loadMultiple() as $machine_name => $bundle) {
foreach ($this->entityTypeManager->getStorage('features_bundle')->loadMultiple() as $machine_name => $bundle) {
$this->bundles[$machine_name] = $bundle;
}
}
@@ -297,7 +297,7 @@ class FeaturesAssigner implements FeaturesAssignerInterface {
// config file.
$ext_storage = new ExtensionInstallStorage($this->configStorage);
$record = $ext_storage->read('features.bundle.default');
$bundle_storage = $this->entityManager->getStorage('features_bundle');
$bundle_storage = $this->entityTypeManager->getStorage('features_bundle');
$default = $bundle_storage->createFromStorageRecord($record);
}
@@ -305,6 +305,7 @@ class FeaturesAssigner implements FeaturesAssignerInterface {
$bundle = $default->createDuplicate();
$bundle->setMachineName($machine_name);
$name = !empty($name) ? $name : $machine_name;
$bundle->setName($name);
if (isset($description)) {
$bundle->setDescription($description);
@@ -3,7 +3,7 @@
namespace Drupal\features;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\PluginBase;
/**
@@ -25,11 +25,11 @@ abstract class FeaturesAssignmentMethodBase extends PluginBase implements Featur
protected $assigner;
/**
* The entity manager.
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityManager;
protected $entityTypeManager;
/**
* The configuration factory.
@@ -55,8 +55,8 @@ abstract class FeaturesAssignmentMethodBase extends PluginBase implements Featur
/**
* {@inheritdoc}
*/
public function setEntityManager(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
@@ -4,7 +4,7 @@ namespace Drupal\features;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* Interface for package assignment classes.
@@ -31,10 +31,10 @@ interface FeaturesAssignmentMethodInterface extends PluginInspectionInterface {
/**
* Injects the entity manager.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager to be used to retrieve entity information.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager to be used to retrieve entity information.
*/
public function setEntityManager(EntityManagerInterface $entity_manager);
public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager);
/**
* Injects the configuration factory.
@@ -3,30 +3,78 @@
namespace Drupal\features;
use Drupal\Core\Config\ConfigInstaller;
use Drupal\Core\Config\ConfigInstallerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Config\ConfigManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Class for customizing the test for pre existing configuration.
*
* Copy of ConfigInstaller with findPreExistingConfiguration() modified to
* allow Feature modules to be installed.
* Decorates the ConfigInstaller with findPreExistingConfiguration() modified
* to allow Feature modules to be installed.
*/
class FeaturesConfigInstaller extends ConfigInstaller {
/**
* The configuration installer.
*
* @var \Drupal\Core\Config\ConfigInstallerInterface
*/
protected $configInstaller;
/**
* The features manager.
*
* @var \Drupal\features\FeaturesManagerInterface
*/
protected $featuresManager;
/**
* Constructs the configuration installer.
*
* @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
* The configuration installer.
* @param \Drupal\features\FeaturesManagerInterface $features_manager
* The features manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Config\StorageInterface $active_storage
* The active configuration storage.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
* The configuration manager.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct(ConfigInstallerInterface $config_installer, FeaturesManagerInterface $features_manager, ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher) {
$this->configInstaller = $config_installer;
$this->featuresManager = $features_manager;
list($major, $minor, ) = explode('.', \Drupal::VERSION);
if ($major == 8 && $minor > 2) {
// D8.3 added the %install_profile% argument.
$install_profile = drupal_get_profile();
parent::__construct($config_factory, $active_storage, $typed_config, $config_manager, $event_dispatcher, $install_profile);
}
else {
parent::__construct($config_factory, $active_storage, $typed_config, $config_manager, $event_dispatcher);
}
}
/**
* {@inheritdoc}
*/
protected function findPreExistingConfiguration(StorageInterface $storage) {
// CHANGE START
// Override
// Drupal\Core\Config\ConfigInstaller::findPreExistingConfiguration().
// Allow config that already exists coming from Features.
/** @var \Drupal\features\FeaturesManagerInterface $manager */
$manager = \Drupal::service('features.manager');
$features_config = array_keys($manager->listExistingConfig());
$features_config = array_keys($this->featuresManager->listExistingConfig());
// Map array so we can use isset instead of in_array for faster access.
$features_config = array_combine($features_config, $features_config);
// CHANGE END.
$existing_configuration = array();
// Gather information about all the supported collections.
$collection_info = $this->configManager->getConfigCollectionInfo();
@@ -36,10 +84,8 @@ class FeaturesConfigInstaller extends ConfigInstaller {
$active_storage = $this->getActiveStorages($collection);
foreach ($config_to_create as $config_name) {
if ($active_storage->exists($config_name)) {
// CHANGE START
// Test if config is part of a Feature package.
if (!isset($features_config[$config_name])) {
// CHANGE END.
$existing_configuration[$collection][] = $config_name;
}
}
@@ -48,4 +94,16 @@ class FeaturesConfigInstaller extends ConfigInstaller {
return $existing_configuration;
}
/**
* Creates configuration in a collection based on the provided list.
*
* @param string $collection
* The configuration collection.
* @param array $config_to_create
* An array of configuration data to create, keyed by name.
*/
public function createConfiguration($collection, array $config_to_create) {
return parent::createConfiguration($collection, $config_to_create);
}
}
@@ -82,7 +82,11 @@ abstract class FeaturesGenerationMethodBase implements FeaturesGenerationMethodI
// If this is the profile, its directory is already assigned.
if (!isset($bundle) || !$bundle->isProfilePackage($package->getMachineName())) {
$package->setDirectory($package->getDirectory() . '/' . $full_name);
$current_path = $package->getDirectory();
if (strpos($current_path, $full_name) < strlen($current_path) - strlen($full_name)) {
// Only append package name if it isn't already there.
$package->setDirectory($package->getDirectory() . '/' . $full_name);
}
}
$this->preparePackage($package, $existing_packages, $bundle);
@@ -68,10 +68,8 @@ class FeaturesInstallStorage extends ExtensionInstallStorage {
$listing = new ExtensionDiscovery(\Drupal::root());
// CHANGED START: Add profile directories for any bundles that use a profile.
$profile_directories = [];
if ($profile) {
$profile_directories[] = drupal_get_path('profile', $profile);
}
$listing->setProfileDirectoriesFromSettings();
$profile_directories = $listing->getProfileDirectories();
if ($this->includeProfile) {
// Add any profiles used in bundles.
/** @var \Drupal\features\FeaturesAssignerInterface $assigner */
@@ -8,12 +8,13 @@ use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\config_update\ConfigRevertInterface;
/**
* The FeaturesManager provides helper functions for building packages.
@@ -22,11 +23,11 @@ class FeaturesManager implements FeaturesManagerInterface {
use StringTranslationTrait;
/**
* The entity manager.
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityManager;
protected $entityTypeManager;
/**
* The target storage.
@@ -63,6 +64,13 @@ class FeaturesManager implements FeaturesManagerInterface {
*/
protected $moduleHandler;
/**
* The config reverter.
*
* @var \Drupal\config_update\ConfigRevertInterface
*/
protected $configReverter;
/**
* The Features settings.
*
@@ -117,8 +125,8 @@ class FeaturesManager implements FeaturesManagerInterface {
*
* @param string $root
* The app root.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Config\StorageInterface $config_storage
@@ -127,16 +135,18 @@ class FeaturesManager implements FeaturesManagerInterface {
* The configuration manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\config_update\ConfigRevertInterface $config_reverter
*/
public function __construct($root, EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory,
public function __construct($root, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory,
StorageInterface $config_storage, ConfigManagerInterface $config_manager,
ModuleHandlerInterface $module_handler) {
ModuleHandlerInterface $module_handler, ConfigRevertInterface $config_reverter) {
$this->root = $root;
$this->entityManager = $entity_manager;
$this->entityTypeManager = $entity_type_manager;
$this->configStorage = $config_storage;
$this->configManager = $config_manager;
$this->moduleHandler = $module_handler;
$this->configFactory = $config_factory;
$this->configReverter = $config_reverter;
$this->settings = $config_factory->getEditable('features.settings');
$this->extensionStorages = new FeaturesExtensionStorages($this->configStorage);
$this->extensionStorages->addStorage(InstallStorage::CONFIG_INSTALL_DIRECTORY);
@@ -146,6 +156,15 @@ class FeaturesManager implements FeaturesManagerInterface {
$this->configCollection = [];
}
/**
* {@inheritdoc}
*/
public function setRoot($root) {
$this->root = $root;
// Clear cache.
$this->featureInfoCache = [];
}
/**
* {@inheritdoc}
*/
@@ -168,7 +187,7 @@ class FeaturesManager implements FeaturesManagerInterface {
return $name;
}
$definition = $this->entityManager->getDefinition($type);
$definition = $this->entityTypeManager->getDefinition($type);
$prefix = $definition->getConfigPrefix() . '.';
return $prefix . $name;
}
@@ -182,12 +201,12 @@ class FeaturesManager implements FeaturesManagerInterface {
'name_short' => '',
);
$prefix = FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG . '.';
if (strpos($fullname, $prefix)) {
if (strpos($fullname, $prefix) !== FALSE) {
$result['type'] = FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG;
$result['name_short'] = substr($fullname, strlen($prefix));
}
else {
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$prefix = $definition->getConfigPrefix() . '.';
if (strpos($fullname, $prefix) === 0) {
@@ -277,6 +296,26 @@ class FeaturesManager implements FeaturesManagerInterface {
}
}
/**
* {@inheritdoc}
*/
public function loadPackage($module_name, $any = FALSE) {
$package = $this->getPackage($module_name);
// Load directly from module if packages are not loaded or
// if we want to include ANY module regardless of its a feature.
if ((empty($this->packages) || $any) && !isset($package)) {
$module_list = $this->moduleHandler->getModuleList();
if (!empty($module_list[$module_name])) {
$extension = $module_list[$module_name];
$package = $this->initPackageFromExtension($extension);
$config = $this->listExtensionConfig($extension);
$package->setConfigOrig($config);
$package->setStatus(FeaturesManagerInterface::STATUS_INSTALLED);
}
}
return $package;
}
/**
* {@inheritdoc}
*/
@@ -347,7 +386,7 @@ class FeaturesManager implements FeaturesManagerInterface {
* {@inheritdoc}
*/
public function getExtensionInfo(Extension $extension) {
return \Drupal::service('info_parser')->parse(\Drupal::root() . '/' . $extension->getPathname());
return \Drupal::service('info_parser')->parse($this->root . '/' . $extension->getPathname());
}
/**
@@ -385,11 +424,15 @@ class FeaturesManager implements FeaturesManagerInterface {
$machine_names[] = $bundle->getProfileName();
}
// If we are checking the default bundle, return all features.
if (isset($bundle) && $bundle->isDefault()) {
$bundle = NULL;
}
$modules = $this->getFeaturesModules($bundle);
// Filter to include only the requested packages.
$modules = array_filter($modules, function ($module) use ($bundle, $machine_names) {
$short_name = $bundle->getShortName($module->getName());
return in_array($short_name, $machine_names);
return in_array($module->getName(), $machine_names);
});
$directories = array();
@@ -411,7 +454,7 @@ class FeaturesManager implements FeaturesManagerInterface {
// modules. system_rebuild_module_data() includes only the site's install
// profile directory, while we may need to include a custom profile.
// @see _system_rebuild_module_data().
$listing = new ExtensionDiscovery(\Drupal::root());
$listing = new ExtensionDiscovery($this->root);
$profile_directories = $listing->setProfileDirectoriesFromSettings()->getProfileDirectories();
$installed_profile = $this->drupalGetProfile();
@@ -444,19 +487,10 @@ class FeaturesManager implements FeaturesManagerInterface {
$modules = $this->getAllModules();
// Filter by bundle.
if (isset($bundle)) {
$features_manager = $this;
$modules = array_filter($modules, function ($module) use ($features_manager, $bundle) {
return $features_manager->isFeatureModule($module, $bundle);
});
}
else {
// No bundle filter, but still only return "Feature" modules
$features_manager = $this;
$modules = array_filter($modules, function ($module) use ($features_manager) {
return $features_manager->isFeatureModule($module);
});
}
$features_manager = $this;
$modules = array_filter($modules, function ($module) use ($features_manager, $bundle) {
return $features_manager->isFeatureModule($module, $bundle);
});
// Filtered by installed status.
if ($installed) {
@@ -480,10 +514,14 @@ class FeaturesManager implements FeaturesManagerInterface {
* {@inheritdoc}
*/
public function initPackage($machine_name, $name = NULL, $description = '', $type = 'module', FeaturesBundleInterface $bundle = NULL, Extension $extension = NULL) {
if (!isset($this->packages[$machine_name])) {
return $this->packages[$machine_name] = $this->getPackageObject($machine_name, $name, $description, $type, $bundle, $extension);
if (isset($this->packages[$machine_name])) {
return $this->packages[$machine_name];
}
return $this->packages[$machine_name];
// Also look for existing package within the bundle
elseif (isset($bundle) && isset($this->packages[$bundle->getFullName($machine_name)])) {
return $this->packages[$bundle->getFullName($machine_name)];
}
return $this->packages[$machine_name] = $this->getPackageObject($machine_name, $name, $description, $type, $bundle, $extension);
}
/**
@@ -509,8 +547,7 @@ class FeaturesManager implements FeaturesManagerInterface {
$dependencies = [];
$type = $config->getType();
if ($type != FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG) {
$provider = $this->entityManager->getDefinition($type)
->getProvider();
$provider = $this->entityTypeManager->getDefinition($type)->getProvider();
// Ensure the provider is an installed module and not, for example, 'core'
if (isset($module_list[$provider])) {
$dependencies[] = $provider;
@@ -564,7 +601,7 @@ class FeaturesManager implements FeaturesManagerInterface {
// - it is not flagged as excluded.
$assignable = (!$item->isProviderExcluded() || $is_profile_package) && !$item->isExcluded();
// An item is assignable if it was provided by the current package
$assignable = $assignable || ($item->getProvider() == $package->getFullName());
$assignable = $assignable || ($item->getProvider() == $package->getMachineName());
$excluded_from_package = in_array($package_name, $item->getPackageExcluded());
$already_in_package = in_array($item_name, $package->getConfig());
if (($force || (!$already_assigned && $assignable && !$excluded_from_package)) && !$already_in_package) {
@@ -595,9 +632,13 @@ class FeaturesManager implements FeaturesManagerInterface {
'/block\.block\..*_page_title/',
];
$config_collection = $this->getConfigCollection();
// Reverse sort by key so that child package will claim items before parent
// package. E.g., event_registration will claim before event.
krsort($config_collection);
// Sort by key so that specific package will claim items before general
// package. E.g., event_registration and registration_event will claim
// before event.
uksort($patterns, function($a, $b) {
// Count underscores to determine specificity of the package.
return (int) (substr_count($a, '_') <= substr_count($b, '_'));
});
foreach ($patterns as $pattern => $machine_name) {
if (isset($this->packages[$machine_name])) {
foreach ($config_collection as $item_name => $item) {
@@ -608,7 +649,7 @@ class FeaturesManager implements FeaturesManagerInterface {
}
}
if (!$item->getPackage() && preg_match('/[_\-.]' . $pattern . '[_\-.]/', '.' . $item->getShortName() . '.')) {
if (!$item->getPackage() && preg_match('/(\.|-|_|^)' . $pattern . '(\.|-|_|$)/', $item->getShortName())) {
try {
$this->assignConfigPackage($machine_name, [$item_name]);
}
@@ -826,9 +867,9 @@ class FeaturesManager implements FeaturesManagerInterface {
// If no extension was passed in, look for a match.
if (!isset($extension)) {
$module_list = $this->getFeaturesModules($bundle);
$full_name = $bundle->getFullName($package->getMachineName());
if (isset($module_list[$full_name])) {
$extension = $module_list[$full_name];
$module_name = $package->getMachineName();
if (isset($module_list[$module_name])) {
$extension = $module_list[$module_name];
}
}
@@ -931,32 +972,14 @@ class FeaturesManager implements FeaturesManagerInterface {
*/
protected function addPackageFiles(Package $package) {
$config_collection = $this->getConfigCollection();
// Only add files if there is at least one piece of configuration
// present.
// Always add .info.yml and .features.yml files.
$this->addInfoFile($package);
// Only add files if there is at least one piece of configuration present.
if ($package->getConfig()) {
// Add .info.yml files.
$this->addInfoFile($package);
// Add configuration files.
foreach ($package->getConfig() as $name) {
$config = $config_collection[$name];
$data = $config->getData();
// The _core is site-specific, so don't export it.
unset($data['_core']);
// The UUID is site-specfic, so don't export it.
if ($entity_type_id = $this->configManager->getEntityTypeIdByName($name)) {
unset($data['uuid']);
}
$config->setData($data);
// User roles include all permissions currently assigned to them. To
// avoid extraneous additions, reset permissions.
if ($config->getType() == 'user_role') {
$data = $config->getData();
// Unset and not empty permissions data to prevent loss of configured
// role permissions in the event of a feature revert.
unset($data['permissions']);
$config->setData($data);
}
$package->appendFile([
'filename' => $config->getName() . '.yml',
'subdirectory' => $config->getSubdirectory(),
@@ -994,7 +1017,7 @@ class FeaturesManager implements FeaturesManagerInterface {
*/
public function listConfigTypes($bundles_only = FALSE) {
$definitions = [];
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
if (!$bundles_only || $definition->getBundleOf()) {
$definitions[$entity_type] = $definition;
@@ -1039,7 +1062,7 @@ class FeaturesManager implements FeaturesManagerInterface {
public function listConfigByType($config_type) {
// For a given entity type, load all entities.
if ($config_type && $config_type !== FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG) {
$entity_storage = $this->entityManager->getStorage($config_type);
$entity_storage = $this->entityTypeManager->getStorage($config_type);
$names = [];
foreach ($entity_storage->loadMultiple() as $entity) {
$entity_id = $entity->id();
@@ -1050,7 +1073,7 @@ class FeaturesManager implements FeaturesManagerInterface {
// Handle simple configuration.
else {
$definitions = [];
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$definitions[$entity_type] = $definition;
}
@@ -1303,4 +1326,57 @@ class FeaturesManager implements FeaturesManagerInterface {
return $features_info;
}
/**
* {@inheritdoc}
*/
public function createConfiguration(array $config_to_create) {
$existing_config = $this->getConfigCollection();
// If config data is not specified, load it from the extension storage.
foreach ($config_to_create as $name => $item) {
if (empty($item)) {
$config = $this->configReverter->getFromExtension('', $name);
// For testing purposes, if it couldn't load from a module, get config
// from the cached Config Collection
if (empty($config) && isset($existing_config[$name])) {
$config = $existing_config[$name]->getData();
}
$config_to_create[$name] = $config;
}
}
// Determine which config is new vs existing.
$existing = array_intersect_key($config_to_create, $existing_config);
$new = array_diff_key($config_to_create, $existing);
// The FeaturesConfigInstaller exposes the normally protected createConfiguration
// function from Core ConfigInstaller than handles the creation of new
// config or the changing of existing config.
/** @var \Drupal\features\FeaturesConfigInstaller $config_installer */
$config_installer = \Drupal::service('features.config.installer');
$config_installer->createConfiguration(StorageInterface::DEFAULT_COLLECTION, $config_to_create);
// Collect results for new and updated config.
$new_config = $this->getConfigCollection(TRUE);
$result['updated'] = array_intersect_key($new_config, $existing);
$result['new'] = array_intersect_key($new_config, $new);
return $result;
}
/**
* {@inheritdoc}
*/
public function import($modules, $any = FALSE) {
$result = [];
foreach ($modules as $module_name) {
$package = $this->loadPackage($module_name, $any);
$components = isset($package) ? $package->getConfigOrig() : [];
if (empty($components)) {
continue;
}
$result[$module_name] = $this->createConfiguration(array_fill_keys($components, []));
}
return $result;
}
}
@@ -33,6 +33,14 @@ interface FeaturesManagerInterface {
const STATE_DEFAULT = 0;
const STATE_OVERRIDDEN = 1;
/**
* Set the app.root.
*
* Should only be used by tests.
* @param string $root
*/
public function setRoot($root);
/**
* Returns the active config store.
*
@@ -169,6 +177,21 @@ interface FeaturesManagerInterface {
*/
public function setPackage(Package $package);
/**
* Load a specific package.
*
* Similar to getPackage but can also load modules that are not Features.
*
* @param string $module_name
* Full machine name of module.
* @param bool $any
* If TRUE then check for any module, not just a Features module.
*
* @return \Drupal\features\Package
* Package data.
*/
public function loadPackage($module_name, $any = FALSE);
/**
* Filters the supplied package list by the given namespace.
*
@@ -268,7 +291,7 @@ interface FeaturesManagerInterface {
* (optional) Bundle to use to add profile directories to the scan.
* @param \Drupal\Core\Extension\Extension $extension
* (optional) An Extension object.
* @return array
* @return \Drupal\features\Package
* The created package array.
*/
public function initPackage($machine_name, $name = NULL, $description = '', $type = 'module', FeaturesBundleInterface $bundle = NULL, Extension $extension = NULL);
@@ -507,7 +530,7 @@ interface FeaturesManagerInterface {
public function getExportInfo(Package $package, FeaturesBundleInterface $bundle = NULL);
/**
* Determines if the module is a Features package, optinally testing by
* Determines if the module is a Features package, optionally testing by
* bundle.
*
* @param \Drupal\Core\Extension\Extension $module
@@ -598,4 +621,27 @@ interface FeaturesManagerInterface {
*/
public function getFeaturesInfo(Extension $extension);
/**
* Creates configuration in a collection based on the provided list.
*
* @param array $config_to_create
* An array of configuration data to create, keyed by name.
* @return array of config imported
* 'new': list of new config created keyed by name.
* 'updated': list of updated config keyed by name.
*/
public function createConfiguration(array $config_to_create);
/**
* @param array $modules
* An array of module names to import (revert)
* @param bool $any
* Set to TRUE to import config from non-Features modules
* @return array of config imported
* keyed by name of module, then:
* 'new': list of new config created keyed by name.
* 'updated': list of updated config keyed by name.
*/
public function import($modules, $any = FALSE);
}
@@ -1,24 +0,0 @@
<?php
namespace Drupal\features;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
/**
* Service provider implementation for Features to override config.installer.
*
* @ingroup container
*/
class FeaturesServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
// Override the config.installer class with a new class.
$definition = $container->getDefinition('config.installer');
$definition->setClass('Drupal\features\FeaturesConfigInstaller');
}
}
@@ -151,6 +151,11 @@ class Package {
}
/**
* Return the full name of the package by prefixing it with bundle as needed
*
* NOTE: When possible, use the Bundle::getFullName method since it can
* better handle cases where a bundle is a profile.
*
* @return string
*/
public function getFullName() {
@@ -0,0 +1,63 @@
<?php
namespace Drupal\features\Plugin\FeaturesAssignment;
use Drupal\features\FeaturesAssignmentMethodBase;
use Drupal\features\FeaturesManagerInterface;
/**
* Class for excluding configuration from packages.
*
* @Plugin(
* id = "alter",
* weight = 0,
* name = @Translation("Alter"),
* description = @Translation("Alter configuration items before they are exported. Altering includes options such as removing permissions from roles."),
* config_route_name = "features.assignment_alter",
* default_settings = {
* "core" = TRUE,
* "uuid" = TRUE,
* "user_permissions" = TRUE,
* }
* )
*/
class FeaturesAssignmentAlter extends FeaturesAssignmentMethodBase {
/**
* {@inheritdoc}
*/
public function assignPackages($force = FALSE) {
$current_bundle = $this->assigner->getBundle();
$settings = $current_bundle->getAssignmentSettings($this->getPluginId());
// Alter configuration items.
if ($settings['core'] || $settings['uuid'] || $settings['user_permissions']) {
$config_collection = $this->featuresManager->getConfigCollection();
foreach ($config_collection as &$config) {
$data = $config->getData();
if ($settings['core']) {
unset($data['_core']);
}
// Unset UUID for configuration entities.
if ($settings['uuid'] && $config->getType() !== FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG) {
unset($data['uuid']);
}
// Unset permissions for user roles. Doing so facilitates packaging
// roles that may have permissions that relate to multiple packages.
if ($settings['user_permissions'] && $config->getType() == 'user_role') {
// Unset and not empty permissions data to prevent loss of configured
// role permissions in the event of a feature revert.
unset($data['permissions']);
}
$config->setData($data);
}
// Clean up the $config pass by reference.
unset($config);
// Register the updated data.
$this->featuresManager->setConfigCollection($config_collection);
}
}
}
@@ -49,12 +49,11 @@ class FeaturesAssignmentBaseType extends FeaturesAssignmentMethodBase {
catch (\Exception $exception) {
\Drupal::logger('features')->error($exception->getMessage());
}
$this->featuresManager->assignConfigDependents([$item_name]);
}
}
}
$entity_types = $this->entityManager->getDefinitions();
$entity_types = $this->entityTypeManager->getDefinitions();
$content_base_types = $settings['types']['content'];
foreach ($content_base_types as $entity_type_id) {
@@ -29,8 +29,8 @@ class FeaturesAssignmentCoreType extends FeaturesAssignmentMethodBase {
$machine_name = 'core';
$name = $this->t('Core');
$description = $this->t('Provides core components required by other features.');
$this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
$this->assignPackageByConfigTypes($machine_name, $force);
$package = $this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
$this->assignPackageByConfigTypes($package->getMachineName(), $force);
}
@@ -11,7 +11,7 @@ use Drupal\features\FeaturesAssignmentMethodBase;
* id = "exclude",
* weight = -5,
* name = @Translation("Exclude"),
* description = @Translation("Exclude configuration items from packaging by various methods including by configuration type."),
* description = @Translation("Exclude configuration items from packaging by various methods including by configuration type. When configuration is excluded, it won't be automatically reassigned to other packages."),
* config_route_name = "features.assignment_exclude",
* default_settings = {
* "curated" = FALSE,
@@ -83,8 +83,9 @@ class FeaturesAssignmentExclude extends FeaturesAssignmentMethodBase {
if ($module_namespace) {
$modules = $this->featuresManager->getFeaturesModules($current_bundle);
foreach ($modules as $extension) {
// Only make exception for non-exported modules
if (!empty($exclude_module['namespace_any']) || !isset($all_modules[$extension->getName()])) {
// Only make exception for uninstalled modules or
// if namespace_any is set
if (!empty($exclude_module['namespace_any']) || !$this->featuresManager->extensionEnabled($extension)) {
$extension_list = array_merge($extension_list, $this->featuresManager->listExtensionConfig($extension));
}
}
@@ -11,7 +11,7 @@ use Drupal\features\FeaturesAssignmentMethodBase;
* id = "namespace",
* weight = 0,
* name = @Translation("Namespace"),
* description = @Translation("Add to packages configuration with a machine name containing that package's machine name."),
* description = @Translation("Add config to packages that contain that package's machine name."),
* )
*/
class FeaturesAssignmentNamespace extends FeaturesAssignmentMethodBase {
@@ -19,8 +19,20 @@ class FeaturesAssignmentNamespace extends FeaturesAssignmentMethodBase {
* {@inheritdoc}
*/
public function assignPackages($force = FALSE) {
$packages = array_keys($this->featuresManager->getPackages());
$this->featuresManager->assignConfigByPattern(array_combine($packages, $packages));
$packages = $this->featuresManager->getPackages();
$current_bundle = $this->assigner->getBundle();
// Build an array of patterns.
// Keys are short names while values are full machine names.
// We need full names because existing packages may receive machine names
// prefixed with a bundle name.
$patterns = [];
foreach ($packages as $package) {
$machine_name = $package->getMachineName();
$pattern = $current_bundle->getShortName($machine_name);
$patterns[$pattern] = $machine_name;
}
$this->featuresManager->assignConfigByPattern($patterns);
}
}
@@ -29,8 +29,8 @@ class FeaturesAssignmentSiteType extends FeaturesAssignmentMethodBase {
$machine_name = 'site';
$name = $this->t('Site');
$description = $this->t('Provides site components.');
$this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
$this->assignPackageByConfigTypes($machine_name, $force);
$package = $this->featuresManager->initPackage($machine_name, $name, $description, 'module', $current_bundle);
$this->assignPackageByConfigTypes($package->getMachineName(), $force);
}
}
@@ -22,6 +22,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements ContainerFactoryPluginInterface {
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* The CSRF token generator.
*
@@ -32,10 +39,13 @@ class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements
/**
* Creates a new FeaturesGenerationArchive instance.
*
* @param string $root
* The app root.
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
* The CSRF token generator.
*/
public function __construct(\Drupal\Core\Access\CsrfTokenGenerator $csrf_token) {
public function __construct($root, \Drupal\Core\Access\CsrfTokenGenerator $csrf_token) {
$this->root = $root;
$this->csrfToken = $csrf_token;
}
@@ -44,6 +54,7 @@ class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('app.root'),
$container->get('csrf_token')
);
}
@@ -66,8 +77,19 @@ class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements
protected function preparePackage(Package $package, array $existing_packages, FeaturesBundleInterface $bundle = NULL) {
if (isset($existing_packages[$package->getMachineName()])) {
$existing_directory = $existing_packages[$package->getMachineName()];
}
else {
$existing_directory = $package->getDirectory();
}
$existing_directory = $this->root . '/' . $existing_directory;
if (is_dir($existing_directory)) {
// Scan for all files.
$files = file_scan_directory($existing_directory, '/.*/');
// Skip any existing .features.yml as it will be replaced.
$exclude_files = [
$package->getMachineName() . '.features',
];
foreach ($files as $file) {
// Skip files in the any existing configuration directory, as these
// will be replaced.
@@ -83,7 +105,7 @@ class FeaturesGenerationArchive extends FeaturesGenerationMethodBase implements
$package->setFiles($files);
}
// Read in remaining files.
else {
elseif (!in_array($file->name, $exclude_files)) {
// Determine if the file is within a subdirectory of the
// extension's directory.
$file_directory = dirname($file->uri);
@@ -3,6 +3,7 @@
namespace Drupal\features\Plugin\FeaturesGeneration;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\features\FeaturesGenerationMethodBase;
use Drupal\features\FeaturesBundleInterface;
use Drupal\features\Package;
@@ -32,14 +33,21 @@ class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements Co
*/
protected $root;
/**
* The file_system service.
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* Creates a new FeaturesGenerationWrite instance.
*
* @param string $root
* The app root.
*/
public function __construct($root) {
public function __construct($root, FileSystemInterface $fileSystem) {
$this->root = $root;
$this->fileSystem = $fileSystem;
}
/**
@@ -47,7 +55,8 @@ class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements Co
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('app.root')
$container->get('app.root'),
$container->get('file_system')
);
}
@@ -65,19 +74,22 @@ class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements Co
// If this package is already present, prepare files.
if (isset($existing_packages[$package->getMachineName()])) {
$existing_directory = $existing_packages[$package->getMachineName()];
$package->setDirectory($existing_directory);
}
else {
$existing_directory = $package->getDirectory();
}
// Merge in the info file.
$info_file_uri = $this->root . '/' . $existing_directory . '/' . $package->getMachineName() . '.info.yml';
if (file_exists($info_file_uri)) {
$files = $package->getFiles();
$files['info']['string'] = $this->mergeInfoFile($package->getFiles()['info']['string'], $info_file_uri);
$package->setFiles($files);
}
// Merge in the info file.
$info_file_uri = $this->root . '/' . $existing_directory . '/' . $package->getMachineName() . '.info.yml';
if (file_exists($info_file_uri)) {
$files = $package->getFiles();
$files['info']['string'] = $this->mergeInfoFile($package->getFiles()['info']['string'], $info_file_uri);
$package->setFiles($files);
// Remove the config directories, as they will be replaced.
foreach (array_keys($this->featuresManager->getExtensionStorages()->getExtensionStorages()) as $directory) {
foreach (array_keys($this->featuresManager->getExtensionStorages()
->getExtensionStorages()) as $directory) {
$config_directory = $this->root . '/' . $existing_directory . '/' . $directory;
if (is_dir($config_directory)) {
file_unmanaged_delete_recursive($config_directory);
@@ -213,7 +225,7 @@ class FeaturesGenerationWrite extends FeaturesGenerationMethodBase implements Co
}
$directory = $this->root . '/' . $directory;
if (!is_dir($directory)) {
if (drupal_mkdir($directory, NULL, TRUE) === FALSE) {
if ($this->fileSystem->mkdir($directory, NULL, TRUE) === FALSE) {
throw new \Exception($this->t('Failed to create directory @directory.', ['@directory' => $directory]));
}
}
@@ -1,136 +0,0 @@
<?php
/**
* This file was generated via php core/scripts/generate-proxy-class.php 'Drupal\features\FeaturesConfigInstaller' "modules/contrib/features/src".
*/
namespace Drupal\features\ProxyClass {
/**
* Provides a proxy class for \Drupal\features\FeaturesConfigInstaller.
*
* @see \Drupal\Component\ProxyBuilder
*/
class FeaturesConfigInstaller implements \Drupal\Core\Config\ConfigInstallerInterface
{
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* The id of the original proxied service.
*
* @var string
*/
protected $drupalProxyOriginalServiceId;
/**
* The real proxied service, after it was lazy loaded.
*
* @var \Drupal\features\FeaturesConfigInstaller
*/
protected $service;
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Constructs a ProxyClass Drupal proxy object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container.
* @param string $drupal_proxy_original_service_id
* The service ID of the original service.
*/
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
{
$this->container = $container;
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
}
/**
* Lazy loads the real service from the container.
*
* @return object
* Returns the constructed real service.
*/
protected function lazyLoadItself()
{
if (!isset($this->service)) {
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
}
return $this->service;
}
/**
* {@inheritdoc}
*/
public function installDefaultConfig($type, $name)
{
return $this->lazyLoadItself()->installDefaultConfig($type, $name);
}
/**
* {@inheritdoc}
*/
public function installOptionalConfig(\Drupal\Core\Config\StorageInterface $storage = NULL, $dependency = array (
))
{
return $this->lazyLoadItself()->installOptionalConfig($storage, $dependency);
}
/**
* {@inheritdoc}
*/
public function installCollectionDefaultConfig($collection)
{
return $this->lazyLoadItself()->installCollectionDefaultConfig($collection);
}
/**
* {@inheritdoc}
*/
public function setSourceStorage(\Drupal\Core\Config\StorageInterface $storage)
{
return $this->lazyLoadItself()->setSourceStorage($storage);
}
/**
* {@inheritdoc}
*/
public function getSourceStorage()
{
return $this->lazyLoadItself()->getSourceStorage();
}
/**
* {@inheritdoc}
*/
public function setSyncing($status)
{
return $this->lazyLoadItself()->setSyncing($status);
}
/**
* {@inheritdoc}
*/
public function isSyncing()
{
return $this->lazyLoadItself()->isSyncing();
}
/**
* {@inheritdoc}
*/
public function checkConfigurationToInstall($type, $name)
{
return $this->lazyLoadItself()->checkConfigurationToInstall($type, $name);
}
}
}
@@ -0,0 +1,7 @@
langcode: en
status: true
dependencies: { }
id: short
label: 'Default short date'
locked: false
pattern: 'm/d/Y - H:i'
@@ -1,4 +1,3 @@
bundle: test
excluded:
- system.theme
required: true
@@ -6,8 +6,8 @@ package: Test
dependencies:
- features
# Information added by Drupal.org packaging script on 2016-09-02
version: '8.x-3.0-beta8'
# Information added by Drupal.org packaging script on 2017-03-07
version: '8.x-3.5'
core: '8.x'
project: 'features'
datestamp: 1472847281
datestamp: 1488908587
@@ -0,0 +1,7 @@
langcode: en
status: true
dependencies: { }
id: long
label: 'Default long date'
locked: false
pattern: 'l, F j, Y - H:i'
@@ -0,0 +1,3 @@
bundle: test_mybundle
excluded:
- system.theme
@@ -0,0 +1,13 @@
name: Test Core
type: module
# core: 8.x
package: Test
# Ensure features is enabled first
dependencies:
- features
# Information added by Drupal.org packaging script on 2017-03-07
version: '8.x-3.5'
core: '8.x'
project: 'features'
datestamp: 1488908587
@@ -4,6 +4,8 @@ namespace Drupal\Tests\features\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\features\ConfigurationItem;
use Drupal\features\FeaturesManagerInterface;
use Drupal\Core\Config\InstallStorage;
/**
* @group features
@@ -11,11 +13,15 @@ use Drupal\features\ConfigurationItem;
class FeaturesAssignTest extends KernelTestBase {
const PACKAGE_NAME = 'my_test_package';
// Installed test feature package
const TEST_INSTALLED_PACKAGE = 'test_mybundle_core';
// Uninstalled test feature package
const TEST_UNINSTALLED_PACKAGE = 'test_feature';
/**
* {@inheritdoc}
*/
public static $modules = ['features', 'node', 'system', 'user'];
public static $modules = ['features', 'node', 'system', 'user', self::TEST_INSTALLED_PACKAGE];
/**
* @var \Drupal\features\FeaturesManager
@@ -27,6 +33,11 @@ class FeaturesAssignTest extends KernelTestBase {
*/
protected $assigner;
/**
* @var \Drupal\features\FeaturesBundleInterface
*/
protected $bundle;
/**
* @todo Remove the disabled strict config schema checking.
*/
@@ -40,18 +51,129 @@ class FeaturesAssignTest extends KernelTestBase {
$this->installConfig('features');
$this->installConfig('system');
\Drupal::configFactory()->getEditable('features.settings')
->set('assignment.enabled', [])
->set('bundle.settings', [])
->save();
$this->featuresManager = \Drupal::service('features.manager');
$this->assigner = \Drupal::service('features_assigner');
$this->bundle = $this->assigner->getBundle();
// Turn off all assignment plugins.
$this->bundle->setEnabledAssignments([]);
// Start with an empty configuration collection.
$this->featuresManager->setConfigCollection([]);
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentAlter
*/
public function testAssignAlter() {
$method_id = 'alter';
// Enable the method.
$this->enableAssignmentMethod($method_id);
// Add some configuration.
$this->addConfigurationItem('example.settings', [
'_core' => ['something'],
'uuid' => 'something',
],
[
'type' => FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG,
]);
$this->addConfigurationItem('node.type.article', [
'_core' => ['something'],
'uuid' => 'something',
'permissions' => [
'first',
'second',
],
],
[
'type' => 'node_type',
]);
$this->addConfigurationItem('user.role.test', [
'_core' => ['something'],
'uuid' => 'something',
'permissions' => [
'first',
'second',
],
],
[
'type' => 'user_role',
]);
// Set all settings to FALSE.
$settings = [
'core' => FALSE,
'uuid' => FALSE,
'user_permissions' => FALSE,
];
$this->bundle->setAssignmentSettings($method_id, $settings);
$this->assigner->applyAssignmentMethod($method_id);
$config = $this->featuresManager->getConfigCollection();
$this->assertNotEmpty($config['example.settings'], 'Expected config not created.');
$this->assertNotEmpty($config['node.type.article'], 'Expected config not created.');
$this->assertNotEmpty($config['user.role.test'], 'Expected config not created.');
$example_settings_data = $config['example.settings']->getData();
$this->assertEquals($example_settings_data['_core'], ['something'], 'Expected _core value missing.');
$this->assertEquals($example_settings_data['uuid'], 'something', 'Expected uuid value missing.');
$node_type_data = $config['node.type.article']->getData();
$this->assertEquals($node_type_data['_core'], ['something'], 'Expected _core value missing.');
$this->assertEquals($node_type_data['uuid'], 'something', 'Expected uuid value missing.');
$this->assertEquals($node_type_data['permissions'], [
'first',
'second',
], 'Expected permissions value missing.');
$user_role_data = $config['user.role.test']->getData();
$this->assertEquals($user_role_data['_core'], ['something'], 'Expected _core value missing.');
$this->assertEquals($user_role_data['uuid'], 'something', 'Expected uuid value missing.');
$this->assertEquals($user_role_data['permissions'], [
'first',
'second',
], 'Expected permissions value missing.');
// Set all settings to TRUE.
$settings = [
'core' => TRUE,
'uuid' => TRUE,
'user_permissions' => TRUE,
];
$this->bundle->setAssignmentSettings($method_id, $settings);
$this->assigner->applyAssignmentMethod($method_id);
$config = $this->featuresManager->getConfigCollection();
$this->assertNotEmpty($config['example.settings'], 'Expected config not created.');
$this->assertNotEmpty($config['node.type.article'], 'Expected config not created.');
$this->assertNotEmpty($config['user.role.test'], 'Expected config not created.');
$example_settings_data = $config['example.settings']->getData();
$this->assertFalse(isset($example_settings_data['_core']), 'Unexpected _core value present.');
// uuid should be retained for simple configuration.
$this->assertEquals($example_settings_data['uuid'], 'something', 'Expected uuid value missing.');
$node_type_data = $config['node.type.article']->getData();
$this->assertFalse(isset($node_type_data['_core']), 'Unexpected _core value present.');
$this->assertFalse(isset($node_type_data['uuid']), 'Unexpected uuid value present.');
// permissions should be stripped only for user_role configuration.
$this->assertEquals($node_type_data['permissions'], [
'first',
'second',
], 'Expected permissions value missing.');
$user_role_data = $config['user.role.test']->getData();
$this->assertFalse(isset($user_role_data['_core']), 'Unexpected _core value present.');
$this->assertFalse(isset($user_role_data['uuid']), 'Unexpected uuid value present.');
$this->assertFalse(isset($user_role_data['permissions']), 'Unexpected permissions value present.');
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentBaseType
*/
@@ -86,15 +208,14 @@ class FeaturesAssignTest extends KernelTestBase {
$expected_package_names = ['article', 'user'];
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
// Dependents like field.field.node.article.body should not be assigned.
$expected_config_items = [
'node.type.article',
'field.field.node.article.body',
];
$this->assertEquals($expected_config_items, $packages['article']->getConfig(), 'Expected configuration items not present in article package.');
}
/**
@@ -130,11 +251,10 @@ class FeaturesAssignTest extends KernelTestBase {
$expected_package_names = ['core'];
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
$this->assertTrue(in_array('field.storage.node.body', $packages['core']->getConfig(), 'Expected configuration item not present in core package.'));
$this->assertFalse(in_array('field.field.node.article.body', $packages['core']->getConfig(), 'Unexpected configuration item present in core package.'));
}
/**
@@ -182,7 +302,168 @@ class FeaturesAssignTest extends KernelTestBase {
];
$this->assertEquals($expected_config_items, $packages[self::PACKAGE_NAME]->getConfig(), 'Expected configuration items not present in article package.');
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentExclude
*/
public function testAssignExclude() {
$method_id = 'exclude';
// Enable the method.
$this->enableAssignmentMethod($method_id);
// Also enable Packages and Core plugins.
$this->enableAssignmentMethod('packages', FALSE);
$this->enableAssignmentMethod('core', FALSE);
// Apply the bundle
$this->bundle = $this->assigner->loadBundle('test_mybundle');
$this->assigner->applyAssignmentMethod('packages');
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages[self::TEST_INSTALLED_PACKAGE], 'Expected package not created.');
// 1. When Required is set to True, config should stay with the module
// First, test with "Required" set to True.
$packages[self::TEST_INSTALLED_PACKAGE]->setRequired(true);
$this->featuresManager->setPackages($packages);
$this->assigner->applyAssignmentMethod('exclude');
$this->assigner->applyAssignmentMethod('core');
$this->assigner->applyAssignmentMethod('existing');
$packages = $this->featuresManager->getPackages();
$expected_config_items = [
'core.date_format.long',
];
$this->assertEquals($expected_config_items, $packages[self::TEST_INSTALLED_PACKAGE]->getConfig(), 'Expected configuration items not present in existing test_core package.');
// 2. When Required is set to False, config still stays with module
// Because the module is installed.
$this->reset();
$this->bundle = $this->assigner->loadBundle('test_mybundle');
$this->assigner->applyAssignmentMethod('packages');
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages[self::TEST_INSTALLED_PACKAGE], 'Expected test_mybundle_core package not created.');
// Set "Required" set to False
$packages[self::TEST_INSTALLED_PACKAGE]->setRequired(false);
$this->featuresManager->setPackages($packages);
$this->assigner->applyAssignmentMethod('exclude');
$this->assigner->applyAssignmentMethod('core');
$this->assigner->applyAssignmentMethod('existing');
$packages = $this->featuresManager->getPackages();
$this->assertFalse(array_key_exists('core', $packages), 'Core package should not be created.');
$expected_config_items = [
'core.date_format.long',
];
$this->assertEquals($expected_config_items, $packages[self::TEST_INSTALLED_PACKAGE]->getConfig(), 'Expected configuration items not present in existing test_core package.');
// 3. When Required is set to False and module is NOT installed,
// Config stays with module if it doesn't match the current namespace
$this->reset();
// Load a bundle different from TEST_UNINSTALLED_PACKAGE
$this->bundle = $this->assigner->loadBundle('test_mybundle');
$this->assigner->applyAssignmentMethod('packages');
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages[self::TEST_UNINSTALLED_PACKAGE], 'Expected test_feature package not created.');
$this->assertNotEmpty($packages[self::TEST_INSTALLED_PACKAGE], 'Expected test_mybundle_core package not created.');
// Mark package as uninstalled, set "Required" set to False
$packages[self::TEST_UNINSTALLED_PACKAGE]->setRequired(false);
$this->featuresManager->setPackages($packages);
$this->assigner->applyAssignmentMethod('exclude');
$this->assigner->applyAssignmentMethod('core');
$this->assigner->applyAssignmentMethod('existing');
$packages = $this->featuresManager->getPackages();
$this->assertFalse(array_key_exists('core', $packages), 'Core package should not be created.');
$expected_config_items = [
'core.date_format.short',
'system.cron',
];
$this->assertEquals($expected_config_items, $packages[self::TEST_UNINSTALLED_PACKAGE]->getConfig(), 'Expected configuration items not present in existing test_feature package.');
// 4. When Required is set to False and module is NOT installed,
// Config is reassigned within modules that match the namespace.
$this->reset();
// Load the bundle used in TEST_UNINSTALLED_PACKAGE
$this->bundle = $this->assigner->loadBundle('test');
if (empty($this->bundle) || $this->bundle->isDefault()) {
// Since we uninstalled the test_feature, we probably need to create
// an empty "test" bundle
$this->bundle = $this->assigner->createBundleFromDefault('test');
}
$this->assigner->applyAssignmentMethod('packages');
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages[self::TEST_UNINSTALLED_PACKAGE], 'Expected test_feature package not created.');
// Set "Required" set to False
$packages[self::TEST_UNINSTALLED_PACKAGE]->setRequired(false);
$this->featuresManager->setPackages($packages);
$this->assigner->applyAssignmentMethod('exclude');
$this->assigner->applyAssignmentMethod('core');
$this->assigner->applyAssignmentMethod('existing');
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages['core'], 'Expected Core package not created.');
// Ensure "core" package is not confused with "test_core" module
// Since we are in a bundle
$this->assertEmpty($packages['core']->getExtension(), 'Autogenerated core package should not have an extension');
// Core config should be reassigned from TEST_UNINSTALLED_PACKAGE into Core
$expected_config_items = [
'system.cron',
];
$this->assertEquals($expected_config_items, $packages[self::TEST_UNINSTALLED_PACKAGE]->getConfig(), 'Expected configuration items not present in existing test_feature package.');
$expected_config_items = [
'core.date_format.short',
];
$this->assertEquals($expected_config_items, $packages['core']->getConfig(), 'Expected configuration items not present in core package.');
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentExclude
*/
public function testAssignExisting() {
$method_id = 'existing';
// Enable the method.
$this->enableAssignmentMethod($method_id);
// Also enable Packages plugin.
$this->enableAssignmentMethod('packages', FALSE);
// First create the existing packages.
$this->assigner->applyAssignmentMethod('packages');
// Now move config into those existing packages.
$this->assigner->applyAssignmentMethod($method_id);
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages[self::TEST_INSTALLED_PACKAGE], 'Expected package not created.');
$this->assertNotEmpty($packages[self::TEST_UNINSTALLED_PACKAGE], 'Expected package not created.');
// Turn off any "required" option in package to let config get reassigned
$package = $packages[self::TEST_INSTALLED_PACKAGE];
$package->setRequired(true);
$expected_config_items = [
'core.date_format.long',
];
$this->assertEquals($expected_config_items, $packages[self::TEST_INSTALLED_PACKAGE]->getConfig(), 'Expected configuration items not present in existing package.');
$expected_config_items = [
'core.date_format.short',
'system.cron',
];
$this->assertEquals($expected_config_items, $packages[self::TEST_UNINSTALLED_PACKAGE]->getConfig(), 'Expected configuration items not present in existing package.');
}
/**
@@ -258,6 +539,254 @@ class FeaturesAssignTest extends KernelTestBase {
$this->assertEquals($expected_config_items, $actual_config_items, 'Expected configuration items not present in article package.');
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentNamespace
*/
public function testAssignNamespace() {
$method_id = 'namespace';
// Enable the method.
$this->enableAssignmentMethod($method_id);
// Apply the bundle
$this->bundle = $this->assigner->loadBundle('test_mybundle');
$package_data = [
'article' => [
// Items that should be assigned to 'article'.
'article',
'article-after',
'before.article',
'something_article',
'something-article',
'something.article',
'article_something',
'article-something',
'article.something',
'something_article_something',
'something-article-something',
'something.article.something',
'something.article_something',
],
'article_after' => [
// Items that should be assigned to 'article_after'.
'article_after',
'something_article_after',
'something-article_after',
'something.article_after',
'article_after_something',
'article_after-something',
'article_after.something',
'something_article_after_something',
'something-article_after-something',
'something.article_after.something',
'something.article_after_something',
],
'before_article' => [
// Items that should be assigned to 'before_article'.
'before_article',
'something_before_article',
'something-before_article',
'something.before_article',
'before_article_something',
'before_article-something',
'before_article.something',
'something_before_article_something',
'something-before_article-something',
'something.before_article.something',
'something.before_article_something',
],
// Emulate an existing feature, which has a machine name prefixed by
// the bundle name.
'test_mybundle_page' => [
// Items that should be assigned to 'test_mybundle_page'.
// Items should match the short name, 'page'.
'page',
'page-after',
'before.page',
'something_page',
'something-page',
'something.page',
'page_something',
'page-something',
'page.something',
'something_page_something',
'something-page-something',
'something.page.something',
'something.page_something',
],
];
foreach ($package_data as $machine_name => $config_short_names) {
$this->featuresManager->initPackage($machine_name, 'My test package ' . $machine_name);
foreach ($config_short_names as $short_name) {
$this->addConfigurationItem('node.type.' . $short_name, [], [
'type' => 'node_type',
'shortName' => $short_name,
]);
}
}
// Add some config that should not be matched.
$config_short_names = [
'example',
'example_something',
'article~',
'myarticle',
];
foreach ($config_short_names as $short_name) {
$this->addConfigurationItem('node.type.' . $short_name, [], [
'type' => 'node_type',
'shortName' => $short_name,
]);
}
$this->assigner->applyAssignmentMethod($method_id);
$packages = $this->featuresManager->getPackages();
foreach ($package_data as $machine_name => $config_short_names) {
$this->assertNotEmpty($packages[$machine_name], 'Expected package ' . $machine_name . ' not created.');
array_walk($config_short_names, function(&$value) {
$value = 'node.type.' . $value;
});
sort($config_short_names);
$package_config = $packages[$machine_name]->getConfig();
sort($package_config);
$this->assertEquals($config_short_names, $package_config, 'Expected configuration items not present in ' . $machine_name . ' package.');
}
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentOptionalType
*/
public function testAssignOptionalType() {
$method_id = 'optional';
// Enable the method.
$this->enableAssignmentMethod($method_id);
$settings = [
'types' => [
'config' => ['image_style'],
],
];
$this->bundle->setAssignmentSettings($method_id, $settings);
// Add some configuration.
$this->addConfigurationItem('node.type.article', [], [
'type' => 'node_type',
]);
$this->addConfigurationItem('image.style.test', [], [
'type' => 'image_style',
]);
$this->featuresManager->initPackage(self::PACKAGE_NAME, 'My test package');
$this->assigner->applyAssignmentMethod($method_id);
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages[self::PACKAGE_NAME], 'Expected package not created.');
$config = $this->featuresManager->getConfigCollection();
$this->assertNotEmpty($config['node.type.article'], 'Expected config not created.');
$this->assertNotEmpty($config['image.style.test'], 'Expected config not created.');
$this->assertNull($config['node.type.article']->getSubdirectory(), 'Expected package subdirectory not set to default.');
$this->assertEquals($config['image.style.test']->getSubdirectory(), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, 'Expected package subdirectory not set to optional.');
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentPackages
*/
public function testAssignPackages() {
$method_id = 'packages';
// Enable the method.
$this->enableAssignmentMethod($method_id);
$this->assigner->applyAssignmentMethod($method_id);
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages[self::TEST_INSTALLED_PACKAGE], 'Expected package not created.');
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentProfile
*/
public function testAssignProfile() {
$method_id = 'profile';
// Enable the method.
$this->enableAssignmentMethod($method_id);
// Add some configuration.
$this->addConfigurationItem('shortcut.myshortcut', [], [
'type' => 'shortcut_set',
]);
$this->addConfigurationItem('node.type.article', [], [
'type' => 'node_type',
]);
$this->addConfigurationItem('image.style.test', [], [
'type' => 'image_style',
]);
$this->addConfigurationItem('system.cron', [], [
'type' => 'simple',
]);
$this->bundle = $this->assigner->createBundleFromDefault('myprofile');
$this->bundle->setProfileName('myprofile');
$this->bundle->setIsProfile(TRUE);
$this->assigner->applyAssignmentMethod($method_id);
$packages = $this->featuresManager->getPackages();
$this->assertNotEmpty($packages['myprofile'], 'Expected package not created.');
$expected_config_items = [
'shortcut.myshortcut',
'system.cron',
'system.theme',
];
$this->assertEquals($expected_config_items, $packages['myprofile']->getConfig(), 'Expected configuration items not present in package.');
}
/**
* @covers Drupal\features\Plugin\FeaturesAssignment\FeaturesAssignmentSiteType
*/
public function testAssignSiteType() {
$method_id = 'site';
// Enable the method.
$this->enableAssignmentMethod($method_id);
// Test the default options for the site assignment method.
// Add a piece of configuration of a site type.
$this->addConfigurationItem('filter.format.plain_text', [], [
'shortName' => 'plain_text',
'label' => 'Plain text',
'type' => 'filter_format',
]);
// Add a piece of configuration of a non-site type.
$this->addConfigurationItem('field.field.node.article.body', [], [
'shortName' => 'node.article.body',
'label' => 'Body',
'type' => 'field_config',
'dependents' => [],
]);
$this->assigner->applyAssignmentMethod($method_id);
$packages = $this->featuresManager->getPackages();
$expected_package_names = ['site'];
$this->assertEquals($expected_package_names, array_keys($packages), 'Expected packages not created.');
$this->assertTrue(in_array('filter.format.plain_text', $packages['site']->getConfig(), 'Expected configuration item not present in site package.'));
$this->assertFalse(in_array('field.field.node.article.body', $packages['site']->getConfig(), 'Unexpected configuration item present in site package.'));
}
/**
* Enables a specified assignment method.
*
@@ -268,18 +797,14 @@ class FeaturesAssignTest extends KernelTestBase {
* Defaults to TRUE.
*/
protected function enableAssignmentMethod($method_id, $exclusive = TRUE) {
$settings = \Drupal::configFactory()->getEditable('features.settings');
if ($exclusive) {
$settings->set('assignment.enabled', [$method_id]);
$this->bundle->setEnabledAssignments([$method_id]);
}
else {
$enabled = $settings->get('assignment.enabled');
if (!in_array($method_id, $enabled)) {
$enabled[] = $method_id;
}
$settings->set('assignment.enabled', $enabled);
$enabled = array_keys($this->bundle->getEnabledAssignments());
$enabled[] = $method_id;
$this->bundle->setEnabledAssignments($enabled);
}
$settings->save();
}
/**
@@ -298,4 +823,12 @@ class FeaturesAssignTest extends KernelTestBase {
$this->featuresManager->setConfigCollection($config_collection);
}
/**
* Reset the config to reapply assignment plugins
*/
protected function reset() {
$this->assigner->reset();
// Start with an empty configuration collection.
$this->featuresManager->setConfigCollection([]);
}
}
@@ -4,6 +4,8 @@ namespace Drupal\Tests\features\Kernel;
use Drupal\features\Entity\FeaturesBundle;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Archiver\ArchiveTar;
use org\bovigo\vfs\vfsStream;
/**
@@ -12,6 +14,7 @@ use org\bovigo\vfs\vfsStream;
class FeaturesGenerateTest extends KernelTestBase {
const PACKAGE_NAME = 'my_test_package';
const BUNDLE_NAME = 'giraffe';
/**
* {@inheritdoc}
@@ -43,10 +46,6 @@ class FeaturesGenerateTest extends KernelTestBase {
$this->installConfig('features');
$this->installConfig('system');
\Drupal::configFactory()->getEditable('features.settings')
->set('assignment.enabled', [])
->set('bundle.settings', [])
->save();
$this->featuresManager = \Drupal::service('features.manager');
$this->generator = \Drupal::service('features_generator');
@@ -70,18 +69,33 @@ class FeaturesGenerateTest extends KernelTestBase {
$this->generator->generatePackages('archive', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
$this->assertTrue(file_exists($filename), 'Archive file was not generated.');
$archive = new ArchiveTar($filename);
$files = $archive->listContent();
$this->assertEquals(3, count($files));
$this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml', $files[0]['filename']);
$this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.features.yml', $files[1]['filename']);
$this->assertEquals(self::PACKAGE_NAME . '/config/install/system.site.yml', $files[2]['filename']);
$expected_info = [
"name" => "My test package",
"type" => "module",
"core" => "8.x",
];
$info = Yaml::decode($archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml'));
$this->assertEquals($expected_info, $info, 'Incorrect info file generated');
}
public function testGeneratorWithBundle() {
$filename = file_directory_temp() . '/giraffe_' . self::PACKAGE_NAME . '.tar.gz';
$filename = file_directory_temp() . '/' . self::BUNDLE_NAME . '_' . self::PACKAGE_NAME . '.tar.gz';
if (file_exists($filename)) {
unlink($filename);
}
$this->assertFalse(file_exists($filename), 'Archive file already exists.');
$bundle = FeaturesBundle::create([
'machine_name' => 'giraffe'
'machine_name' => self::BUNDLE_NAME
]);
$this->generator->generatePackages('archive', $bundle, [self::PACKAGE_NAME]);
@@ -89,9 +103,9 @@ class FeaturesGenerateTest extends KernelTestBase {
$package = $this->featuresManager->getPackage(self::PACKAGE_NAME);
$this->assertNull($package);
$package = $this->featuresManager->getPackage('giraffe_' . self::PACKAGE_NAME);
$this->assertEquals('giraffe_' . self::PACKAGE_NAME, $package->getMachineName());
$this->assertEquals('giraffe', $package->getBundle());
$package = $this->featuresManager->getPackage( self::BUNDLE_NAME . '_' . self::PACKAGE_NAME);
$this->assertEquals(self::BUNDLE_NAME . '_' . self::PACKAGE_NAME, $package->getMachineName());
$this->assertEquals(self::BUNDLE_NAME, $package->getBundle());
$this->assertTrue(file_exists($filename), 'Archive file was not generated.');
}
@@ -103,6 +117,7 @@ class FeaturesGenerateTest extends KernelTestBase {
// Set a fake drupal root, so the testbot can also write into it.
vfsStream::setup('drupal');
\Drupal::getContainer()->set('app.root', 'vfs://drupal');
$this->featuresManager->setRoot('vfs://drupal');
$package = $this->featuresManager->getPackage(self::PACKAGE_NAME);
// Find out where package will be exported
@@ -114,9 +129,82 @@ class FeaturesGenerateTest extends KernelTestBase {
$this->assertFalse(file_exists($path), 'Package directory already exists.');
$this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
$info_file_uri = $path . '/' . self::PACKAGE_NAME . '.info.yml';
$this->assertTrue(file_exists($path), 'Package directory was not generated.');
$this->assertTrue(file_exists($path . '/' . self::PACKAGE_NAME . '.info.yml'), 'Package info.yml not generated.');
$this->assertTrue(file_exists($info_file_uri), 'Package info.yml not generated.');
$this->assertTrue(file_exists($path . '/config/install'), 'Package config/install not generated.');
$this->assertTrue(file_exists($path . '/config/install/system.site.yml'), 'Config.yml not exported.');
$expected_info = [
"name" => "My test package",
"type" => "module",
"core" => "8.x",
];
$info = Yaml::decode(file_get_contents($info_file_uri));
$this->assertEquals($expected_info, $info, 'Incorrect info file generated');
// Now, add stuff to the feature and re-export to ensure it is preserved
// Add a dependency to the package itself to see that it gets exported.
$package->setDependencies(['user']);
$this->featuresManager->setPackage($package);
// Add dependency and custom key to the info file to simulate manual edit.
$info['dependencies'] = ['node'];
$info['mykey'] = "test value";
$info_contents = Yaml::encode($info);
file_put_contents($info_file_uri, $info_contents);
// Add an extra file that should be retained.
$css_file = $path . '/' . self::PACKAGE_NAME . '.css';
$file_contents = "This is a dummy file";
file_put_contents($css_file, $file_contents);
// Add a config file that should be removed since it's not part of the
// feature.
$config_file = $path . '/config/install/node.type.mytype.yml';
file_put_contents($config_file, $file_contents);
$this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
$this->assertTrue(file_exists($info_file_uri), 'Package info.yml not generated.');
$expected_info = [
"name" => "My test package",
"type" => "module",
"core" => "8.x",
"dependencies" => ["node", "user"],
"mykey" => "test value",
];
$info = Yaml::decode(file_get_contents($info_file_uri));
$this->assertEquals($expected_info, $info, 'Incorrect info file generated');
$this->assertTrue(file_exists($css_file), 'Extra file was not retained.');
$this->assertFalse(file_exists($config_file), 'Config directory was not cleaned.');
$this->assertEquals($file_contents, file_get_contents($css_file), 'Extra file contents not retained');
// Next, test that generating an Archive picks up the extra files.
$filename = file_directory_temp() . '/' . self::PACKAGE_NAME . '.tar.gz';
if (file_exists($filename)) {
unlink($filename);
}
$this->assertFalse(file_exists($filename), 'Archive file already exists.');
$this->generator->generatePackages('archive', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
$this->assertTrue(file_exists($filename), 'Archive file was not generated.');
$archive = new ArchiveTar($filename);
$files = $archive->listContent();
$this->assertEquals(4, count($files));
$this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml', $files[0]['filename']);
$this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.features.yml', $files[1]['filename']);
$this->assertEquals(self::PACKAGE_NAME . '/config/install/system.site.yml', $files[2]['filename']);
$this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.css', $files[3]['filename']);
$expected_info = [
"name" => "My test package",
"type" => "module",
"core" => "8.x",
"dependencies" => ["node", "user"],
"mykey" => "test value",
];
$info = Yaml::decode($archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml'));
$this->assertEquals($expected_info, $info, 'Incorrect info file generated');
$this->assertEquals($file_contents, $archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.css'), 'Extra file contents not retained');
}
}
@@ -0,0 +1,103 @@
<?php
namespace Drupal\Tests\features\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\features\ConfigurationItem;
use Drupal\features\Package;
/**
* @group features
*/
class FeaturesManagerKernelTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'config', 'features'];
protected $strictConfigSchema = FALSE;
/**
* @var \Drupal\features\FeaturesManagerInterface
*/
protected $featuresManager;
/**
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig('features');
$this->installConfig('system');
$this->featuresManager = $this->container->get('features.manager');
$this->configFactory = $this->container->get('config.factory');
}
/**
* @covers \Drupal\features\FeaturesManager::createConfiguration
*/
public function testCreateConfiguration() {
$config_name = 'system_simple.testcreate';
$config = [
'string_value' => 'example',
'array_value' => [
'item1' => 'value1',
'item2' => 'value2',
],
];
$this->featuresManager->createConfiguration([$config_name => $config]);
$config_item = $this->configFactory->get($config_name);
$this->assertEquals($config['string_value'], $config_item->get('string_value'), 'Test config string saved');
$this->assertEquals($config['array_value'], $config_item->get('array_value'), 'Test config array saved');
}
/**
* @covers \Drupal\features\FeaturesManager::import
*/
public function testImport() {
$packages = [
'package' => new Package('package', [
'configOrig' => ['system_simple.example' => 'system_simple.example'],
'dependencies' => [],
'bundle' => '',
]),
'package2' => new Package('package2', [
'configOrig' => ['system_simple.example2' => 'system_simple.example2'],
'dependencies' => [],
'bundle' => '',
]),
'package3' => new Package('package3', [
'configOrig' => ['system_simple.example3' => 'system_simple.example3'],
'dependencies' => [],
'bundle' => '',
]),
];
$this->featuresManager->setPackages($packages);
// Create all three configuration items.
$config_item = new ConfigurationItem('system_simple.example', ['value' => 'example'], ['package' => 'package']);
$config_item2 = new ConfigurationItem('system_simple.example2', ['value' => 'example2'], ['package' => 'package2']);
$config_item3 = new ConfigurationItem('system_simple.example3', ['value' => 'example3'], ['package' => 'package3']);
// Only save example and example3 as currently active config (so example2 will be new).
$this->featuresManager->setConfigCollection(['system_simple.example' => $config_item, 'system_simple.example3' => $config_item3]);
// Only import example and example2, so example3 is unchanged.
$result = $this->featuresManager->import(['package', 'package2']);
$this->assertEquals(['system_simple.example'], array_keys($result['package']['updated']), 'Expected config updated');
$this->assertEquals(['system_simple.example2'], array_keys($result['package2']['new']), 'Expected config created');
// Test if config was actually saved to the Factory.
// Cannot test for example2 because we didn't save the original config data
// and Package2 isn't a real module so config can't be loaded from module.
$example = $this->configFactory->get('system_simple.example')->get('value');
$this->assertEquals('example', $example, 'Example config saved');
}
}
@@ -5,7 +5,7 @@ namespace Drupal\Tests\features\Unit;
use Drupal\features\Entity\FeaturesBundle;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Prophecy\Prophet;
use Drupal\Core\Site\Settings;
/**
* @coversDefaultClass Drupal\features\Entity\FeaturesBundle
@@ -145,6 +145,47 @@ class FeaturesBundleTest extends UnitTestCase {
}
/**
* @covers ::getFullName
* @covers ::getShortName
* @covers ::SetIsProfile
* @covers ::isProfile
* @covers ::getProfileName
* @covers ::isProfilePackage
* @covers ::inBundle
*/
public function testFullname() {
$bundle = new FeaturesBundle([
'machine_name' => 'mybundle',
'profile_name' => 'mybundle'
], 'mybundle');
$this->assertFalse($bundle->isProfile());
// Settings:get('profile_name') isn't defined in test, so this returns NULL.
$this->assertNull($bundle->getProfileName());
$this->assertFalse($bundle->isProfilePackage('mybundle'));
$this->assertEquals('mybundle_test', $bundle->getFullName('test'));
$this->assertEquals('mybundle_test', $bundle->getFullName('mybundle_test'));
$this->assertEquals('mybundle_mybundle', $bundle->getFullName('mybundle'));
$this->assertEquals('test', $bundle->getShortName('test'));
$this->assertEquals('test', $bundle->getShortName('mybundle_test'));
$this->assertEquals('mybundle', $bundle->getShortName('mybundle_mybundle'));
$this->assertEquals('mybundle', $bundle->getShortName('mybundle'));
$this->assertFalse($bundle->inBundle('test'));
$this->assertTrue($bundle->inBundle('mybundle_test'));
$this->assertFalse($bundle->inBundle('mybundle'));
// Now test it as a profile bundle.
$bundle->setIsProfile(TRUE);
$this->assertTrue($bundle->isProfile());
$this->assertTrue($bundle->isProfilePackage('mybundle'));
$this->assertFalse($bundle->isProfilePackage('standard'));
$this->assertEquals('mybundle', $bundle->getProfileName());
$this->assertEquals('mybundle', $bundle->getFullName('mybundle'));
$this->assertFalse($bundle->inBundle('test'));
$this->assertTrue($bundle->inBundle('mybundle_test'));
$this->assertTrue($bundle->inBundle('mybundle'));
}
}
/**
@@ -13,6 +13,7 @@ use Drupal\Core\Extension\Extension;
use Drupal\Core\Extension\InfoParser;
use Drupal\Core\Extension\InfoParserInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\config_update\ConfigRevertInterface;
use Drupal\features\Entity\FeaturesBundle;
use Drupal\features\FeaturesAssignerInterface;
use Drupal\features\FeaturesBundleInterface;
@@ -42,9 +43,9 @@ class FeaturesManagerTest extends UnitTestCase {
protected $featuresManager;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
protected $entityTypeManager;
/**
* @var \Drupal\Core\Config\StorageInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -66,31 +67,56 @@ class FeaturesManagerTest extends UnitTestCase {
*/
protected $moduleHandler;
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $configReverter;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$container = new ContainerBuilder();
$container->set('string_translation', $this->getStringTranslationStub());
$container->set('app.root', $this->root);
// Since in Drupal 8.3 the "\Drupal::installProfile()" was introduced
// then we have to spoof a value for the "install_profile" parameter
// because it will be used by "ExtensionInstallStorage" class, which
// extends the "FeaturesInstallStorage".
// @see \Drupal\features\FeaturesConfigInstaller::__construct()
$container->setParameter('install_profile', '');
\Drupal::setContainer($container);
$entity_type = $this->getMock('\Drupal\Core\Config\Entity\ConfigEntityTypeInterface');
$entity_type->expects($this->any())
->method('getConfigPrefix')
->willReturn('custom');
$this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
$this->entityManager->expects($this->any())
$entity_type->expects($this->any())
->method('getProvider')
->willReturn('my_module');
$this->entityTypeManager = $this->getMock('\Drupal\Core\Entity\EntityTypeManagerInterface');
$this->entityTypeManager->expects($this->any())
->method('getDefinition')
->willReturn($entity_type);
$this->configFactory = $this->getMock(ConfigFactoryInterface::class);
$this->configStorage = $this->getMock(StorageInterface::class);
$this->configManager = $this->getMock(ConfigManagerInterface::class);
$this->moduleHandler = $this->getMock(ModuleHandlerInterface::class);
$this->featuresManager = new FeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
$string_translation = $this->getStringTranslationStub();
$container = new ContainerBuilder();
$container->set('string_translation', $string_translation);
$container->set('app.root', $this->root);
\Drupal::setContainer($container);
// getModuleList should return an array of extension objects.
// but we just need ::getConfigDependency isset($module_list[$provider]).
$this->moduleHandler->expects($this->any())
->method('getModuleList')
->willReturn(['my_module' => true]);
$this->configReverter = $this->getMock(ConfigRevertInterface::class);
$this->configReverter->expects($this->any())
->method('import')
->willReturn(true);
$this->configReverter->expects($this->any())
->method('revert')
->willReturn(true);
$this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
}
/**
@@ -154,9 +180,12 @@ class FeaturesManagerTest extends UnitTestCase {
/**
* @covers ::setPackage
* @covers ::getPackage
*/
public function testSetPackage() {
// @todo
$package = new Package('foo');
$this->featuresManager->setPackage($package);
$this->assertEquals($package, $this->featuresManager->getPackage('foo'));
}
protected function getAssignInterPackageDependenciesConfigCollection() {
@@ -199,7 +228,7 @@ class FeaturesManagerTest extends UnitTestCase {
$bundle->isDefault()->willReturn(TRUE);
$assigner->getBundle('')->willReturn($bundle->reveal());
// Use the wrapper because we need ::drupalGetProfile().
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
$features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
$features_manager->setAssigner($assigner->reveal());
$features_manager->setConfigCollection($this->getAssignInterPackageDependenciesConfigCollection());
@@ -247,7 +276,7 @@ class FeaturesManagerTest extends UnitTestCase {
$bundle->getMachineName()->willReturn('giraffe');
$assigner->getBundle('giraffe')->willReturn($bundle->reveal());
// Use the wrapper because we need ::drupalGetProfile().
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
$features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
$features_manager->setAssigner($assigner->reveal());
$features_manager->setConfigCollection($this->getAssignInterPackageDependenciesConfigCollection());
@@ -375,7 +404,7 @@ class FeaturesManagerTest extends UnitTestCase {
]);
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $config_storage->reveal(), $this->configManager, $this->moduleHandler);
$features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $config_storage->reveal(), $this->configManager, $this->moduleHandler, $this->configReverter);
$features_manager->setExtensionStorages($extension_storage->reveal());
$this->assertEquals(['test_overridden'], $features_manager->detectOverrides($package));
@@ -409,7 +438,7 @@ class FeaturesManagerTest extends UnitTestCase {
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2']);
$this->assertEquals(['test_config', 'test_config2'], $this->featuresManager->getPackage('test_package')->getConfig());
$this->assertEquals(['example'], $this->featuresManager->getPackage('test_package')->getDependencies());
$this->assertEquals(['example', 'my_module'], $this->featuresManager->getPackage('test_package')->getDependencies());
}
/**
@@ -454,7 +483,7 @@ class FeaturesManagerTest extends UnitTestCase {
\Drupal::getContainer()->set('info_parser', $info_parser->reveal());
$bundle = $this->prophesize(FeaturesBundle::class);
$bundle->getShortName('test_module')->willReturn('test_module');
$bundle->getFullName('test_module')->willReturn('test_module');
$bundle->isDefault()->willReturn(TRUE);
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
@@ -491,7 +520,7 @@ class FeaturesManagerTest extends UnitTestCase {
\Drupal::getContainer()->set('info_parser', $info_parser->reveal());
$bundle = $this->prophesize(FeaturesBundle::class);
$bundle->getShortName('test_module')->willReturn('test_module');
$bundle->getFullName('test_module')->willReturn('test_module');
$bundle->isDefault()->willReturn(TRUE);
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
@@ -521,7 +550,7 @@ class FeaturesManagerTest extends UnitTestCase {
'key' => 'value',
]);
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
$features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
$features_manager->setExtensionStorages($extension_storage->reveal());
$this->assertEmpty($features_manager->detectNew($package));
@@ -533,7 +562,7 @@ class FeaturesManagerTest extends UnitTestCase {
$extension_storage = $this->prophesize(FeaturesExtensionStoragesInterface::class);
$extension_storage->read('test_config')->willReturn(FALSE);
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
$features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
$features_manager->setExtensionStorages($extension_storage->reveal());
$this->assertEquals(['test_config'], $features_manager->detectNew($package));
@@ -574,7 +603,7 @@ class FeaturesManagerTest extends UnitTestCase {
public function testInitPackageWithNewPackage() {
$bundle = new FeaturesBundle(['machine_name' => 'test'], 'features_bundle');
$features_manager = new TestFeaturesManager($this->root, $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
$features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
$features_manager->setAllModules([]);
$package = $features_manager->initPackage('test_feature', 'test name', 'test description', 'module', $bundle);
@@ -597,7 +626,7 @@ class FeaturesManagerTest extends UnitTestCase {
public function testInitPackageWithExistingPackage() {
$bundle = new FeaturesBundle(['machine_name' => 'test'], 'features_bundle');
$features_manager = new TestFeaturesManager('vfs://drupal', $this->entityManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler);
$features_manager = new TestFeaturesManager('vfs://drupal', $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
vfsStream::setup('drupal');
\Drupal::getContainer()->set('app.root', 'vfs://drupal');
@@ -702,7 +731,7 @@ EOT
],
],
]);
$this->featuresManager = new FeaturesManager($this->root, $this->entityManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler);
$this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
$package = new Package('test_feature');
$result = $this->featuresManager->getExportInfo($package);
@@ -721,7 +750,7 @@ EOT
],
],
]);
$this->featuresManager = new FeaturesManager($this->root, $this->entityManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler);
$this->featuresManager = new FeaturesManager($this->root, $this->entityTypeManager, $config_factory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
$package = new Package('test_feature');
$bundle = new FeaturesBundle(['machine_name' => 'test_bundle'], 'features_bundle');
@@ -27,10 +27,6 @@ Devel Generate
=================
Bulk creates nodes, users, comment, terms for development. Has Drush integration.
Devel Node Access
=================
Prints the node_access records for a given node. Also offers hook_node_access_explain for all node access modules to implement.
Devel Generate Extensions
=========================
Devel Images Provider [http://drupal.org/project/devel_image_provider] allows to configure external providers for images.
@@ -9,6 +9,26 @@
"email": "weitzman@tejasa.com",
"homepage": "https://github.com/weitzman",
"role": "Maintainer"
},
{
"name": "Hans Salvisberg",
"email": "drupal@salvisberg.com",
"homepage": "https://www.drupal.org/u/salvis",
"role": "Maintainer"
},
{
"name": "Luca Lusso",
"homepage": "https://www.drupal.org/u/lussoluca",
"role": "Maintainer"
},
{
"name": "Marco (willzyx)",
"homepage": "https://www.drupal.org/u/willzyx",
"role": "Maintainer"
},
{
"name": "See contributors",
"homepage": "https://www.drupal.org/node/3236/committers"
}
],
"support": {
@@ -18,7 +38,7 @@
},
"license": "GPL-2.0+",
"minimum-stability": "dev",
"require": { },
"require": {},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available"
}
@@ -0,0 +1,9 @@
toolbar_items:
- 'devel.admin_settings_link'
- 'devel.cache_clear'
- 'devel.container_info.service'
- 'devel.execute_php'
- 'devel.menu_rebuild'
- 'devel.reinstall'
- 'devel.route_info'
- 'devel.run_cron'
@@ -28,6 +28,16 @@ devel.settings:
type: string
label: 'Devel variable dumper'
devel.toolbar.settings:
type: config_object
label: 'Devel Toolbar settings'
mapping:
toolbar_items:
type: sequence
label: 'Toolbar items'
sequence:
type: string
block.settings.devel_switch_user:
type: block_settings
label: 'Switch user block'
@@ -0,0 +1,38 @@
/**
* @file
* Styling for devel toolbar module.
*/
.toolbar .toolbar-tray-vertical .edit-devel-toolbar {
text-align: right; /* LTR */
padding: 1em;
}
[dir="rtl"] .toolbar .toolbar-tray-vertical .edit-devel-toolbar {
text-align: left;
}
.toolbar .toolbar-tray-horizontal .edit-devel-toolbar {
float: right; /* LTR */
}
[dir="rtl"] .toolbar .toolbar-tray-horizontal .edit-devel-toolbar {
float: left;
}
.toolbar .toolbar-tray-horizontal .toolbar-menu {
float: left; /* LTR */
}
[dir="rtl"] .toolbar .toolbar-tray-horizontal .toolbar-menu {
float: right;
}
.toolbar .toolbar-bar .toolbar-icon-devel:before{
background-image: url(../icons/bebebe/cog.svg);
}
.toolbar-bar .toolbar-icon-devel:active:before,
.toolbar-bar .toolbar-icon-devel.is-active:before {
background-image: url(../icons/ffffff/cog.svg);
}
.toolbar-horizontal .toolbar-horizontal-item-hidden {
display: none;
}

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