contrib modules updates

This commit is contained in:
2019-02-27 10:39:59 +01:00
parent 04a4b8895d
commit e3cf889820
579 changed files with 18343 additions and 4076 deletions
@@ -1,3 +1,21 @@
Admin Toolbar 8.x-1.25, 2018-11-22
----------------------------------
Changes since 8.x-1.24:
- #3012102 by tikaszvince, Jody Lynn, adriancid, grahl, jigarius: Provider
property missing on link definition.
- #3010451 by epowelljr, adriancid: Improve the admin_toolbar module
description.
- #2958415 by adriancid, romainj, Prashant.c, harshita29: Replace usages of the
deprecated drupal_set_message() function.
- #2996485 by idebr, adriancid: Remove unused function
admin_toolbar_tools_get_links().
- #2989281 by JKerschner, adriancid: Consistently use $entityTypeManager.
- #2985106 by Eli-T, adriancid, eme: Add ability to hide Drupal.org links
exposed by admin_toolbar_tools from certain users.
- #2938884 by Spurlos, bdanin, romainj, purdy_nc, adriancid: A non-existent
route breaks the site.
Admin Toolbar 8.x-1.24, 2018-05-28
----------------------------------
Changes since 8.x-1.23:
@@ -1,5 +1,5 @@
name: Admin Toolbar
description: Provides a drop-down menu interface to the site Toolbar.
description: Provides an improved drop-down menu interface to the site Toolbar.
package: Administration
type: module
@@ -8,8 +8,8 @@ type: module
dependencies:
- drupal:toolbar
# Information added by Drupal.org packaging script on 2018-05-28
version: '8.x-1.24'
# Information added by Drupal.org packaging script on 2019-02-03
version: '8.x-1.26'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1527522484
datestamp: 1549218484
@@ -0,0 +1,16 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Admin Toolbar module.
*/
/**
* Rebuild routes to mitigate issue 2938884.
*
* @see https://www.drupal.org/project/admin_toolbar/issues/2938884
*/
function admin_toolbar_update_8001() {
// Rebuilding the route cache.
\Drupal::service("router.builder")->rebuild();
}
@@ -8,8 +8,8 @@ type: module
dependencies:
- admin_toolbar:admin_toolbar
# Information added by Drupal.org packaging script on 2018-05-28
version: '8.x-1.24'
# Information added by Drupal.org packaging script on 2019-02-03
version: '8.x-1.26'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1527522484
datestamp: 1549218484
@@ -7,10 +7,10 @@ type: module
dependencies:
- admin_toolbar:admin_toolbar
- drupal:system (>=8.3)
- drupal:system (>=8.5)
# Information added by Drupal.org packaging script on 2018-05-28
version: '8.x-1.24'
# Information added by Drupal.org packaging script on 2019-02-03
version: '8.x-1.26'
core: '8.x'
project: 'admin_toolbar'
datestamp: 1527522484
datestamp: 1549218484
@@ -74,24 +74,3 @@ admin_toolbar_tools.flush_rendercache:
route_name: admin_toolbar_tools.flush_rendercache
parent: admin_toolbar_tools.flush
menu_name: admin
admin_toolbar_tools.drupalorg:
title: 'Drupal.org'
weight: -5
url: https://www.drupal.org
parent: admin_toolbar_tools.help
menu_name: admin
admin_toolbar_tools.listchanges:
title: 'Change records for Drupal core'
weight: -6
url: https://www.drupal.org/list-changes
parent: admin_toolbar_tools.drupalorg
menu_name: admin
admin_toolbar_tools.doc:
title: 'D8 API documentation'
weight: -5
url: https://api.drupal.org/api/drupal/8
parent: admin_toolbar_tools.drupalorg
menu_name: admin
@@ -48,13 +48,26 @@ function admin_toolbar_tools_help($route_name, RouteMatchInterface $route_match)
* Implements hook_menu_links_discovered_alter().
*/
function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
$languageManager = \Drupal::languageManager();
$config_override_language = $languageManager->getConfigOverrideLanguage();
$languageManager->setConfigOverrideLanguage($languageManager->getLanguage('en'));
$moduleHandler = \Drupal::moduleHandler();
$entityTypeManager = \Drupal::entityTypeManager();
$routeProvider = \Drupal::service('router.route_provider');
$routes = [];
foreach ($routeProvider->getAllRoutes() as $route_name => $route) {
$routes[] = $route_name;
}
/**
* Determine if a route exists by name.
*
* @param $routeName
* The name of the route to check.
*
* @return bool
* Whether a route with that route name exists.
*/
$routeExists = function ($routeName) use ($routeProvider) {
return (count($routeProvider->getRoutesByNames([$routeName])) === 1);
};
$entityTypes = $entityTypeManager->getDefinitions();
$content_entities = [];
@@ -71,14 +84,16 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('views')) {
$links['admin_toolbar_tools.flush_views'] = [
'title' => t('Flush views cache'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'admin_toolbar_tools.flush_views',
'menu_name' => 'admin',
'parent' => 'admin_toolbar_tools.flush',
];
// Adding a menu link to Files.
if ($moduleHandler->moduleExists('file') && in_array('view.files.page_1', $routes)) {
if ($moduleHandler->moduleExists('file') && $routeExists('view.files.page_1')) {
$links['admin_toolbar_tools.view.files'] = [
'title' => t('Files'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'view.files.page_1',
'menu_name' => 'admin',
'parent' => 'system.admin_content',
@@ -93,21 +108,23 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
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)) {
if ($routeExists('entity.' . $content_entity_bundle . '.overview_form')) {
// 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] = [
'title' => t($bundle->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.overview_form',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
'route_parameters' => [$content_entity_bundle => $machine_name],
];
}
if (in_array('entity.' . $content_entity_bundle . '.edit_form', $routes)) {
if ($routeExists('entity.' . $content_entity_bundle . '.edit_form')) {
$key = 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name;
$links[$key] = [
'title' => t($bundle->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.' . $content_entity_bundle . '.collection',
@@ -122,9 +139,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
}
}
if ($moduleHandler->moduleExists('field_ui')) {
if (in_array('entity.' . $content_entity . '.field_ui_fields', $routes)) {
if ($routeExists('entity.' . $content_entity . '.field_ui_fields')) {
$links['entity.' . $content_entity . '.field_ui_fields' . $machine_name] = [
'title' => t('Manage fields'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity . '.field_ui_fields',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
@@ -132,9 +150,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'weight' => 1,
];
}
if (in_array('entity.entity_form_display.' . $content_entity . '.default', $routes)) {
if ($routeExists('entity.entity_form_display.' . $content_entity . '.default')) {
$links['entity.entity_form_display.' . $content_entity . '.default' . $machine_name] = [
'title' => t('Manage form display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_form_display.' . $content_entity . '.default',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
@@ -142,9 +161,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'weight' => 2,
];
}
if (in_array('entity.entity_view_display.' . $content_entity . '.default', $routes)) {
if ($routeExists('entity.entity_view_display.' . $content_entity . '.default')) {
$links['entity.entity_view_display.' . $content_entity . '.default.' . $machine_name] = [
'title' => t('Manage display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_view_display.' . $content_entity . '.default',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
@@ -153,9 +173,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
}
}
if ($moduleHandler->moduleExists('devel') && in_array('entity.' . $content_entity_bundle . '.devel_load', $routes)) {
if ($moduleHandler->moduleExists('devel') && $routeExists('entity.' . $content_entity_bundle . '.devel_load')) {
$links['entity.' . $content_entity_bundle . '.devel_load.' . $machine_name] = [
'title' => t('Devel'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.devel_load',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
@@ -163,9 +184,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
'weight' => 4,
];
}
if (in_array('entity.' . $content_entity_bundle . '.delete_form', $routes)) {
if ($routeExists('entity.' . $content_entity_bundle . '.delete_form')) {
$links['entity.' . $content_entity_bundle . '.delete_form.' . $machine_name] = [
'title' => t('Delete'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.' . $content_entity_bundle . '.delete_form',
'menu_name' => 'admin',
'parent' => $content_entity_bundle_root,
@@ -179,30 +201,35 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
// Add user links.
$links['user.admin_create'] = [
'title' => t('Add a new user'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.admin_create',
'menu_name' => 'admin',
'parent' => 'entity.user.collection',
];
$links['user.admin_permissions'] = [
'title' => t('Permissions'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.admin_permissions',
'menu_name' => 'admin',
'parent' => 'entity.user.collection',
];
$links['entity.user_role.collection'] = [
'title' => t('Roles'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.collection',
'menu_name' => 'admin',
'parent' => 'entity.user.collection',
];
$links['admin_toolbar_tools.user.logout'] = [
'title' => t('Logout'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.logout',
'parent' => 'admin_toolbar_tools.help',
'weight' => 10,
];
$links['user.role_add'] = [
'title' => t('Add a new role'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'user.role_add',
'menu_name' => 'admin',
'parent' => 'entity.user_role.collection',
@@ -211,18 +238,21 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('field_ui')) {
$links['entity.user.field_ui_fields_'] = [
'title' => t('Manage fields'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user.field_ui_fields',
'menu_name' => 'admin',
'parent' => 'entity.user.admin_form',
];
$links['entity.entity_form_display.user.default_'] = [
'title' => t('Manage form display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_form_display.user.default',
'menu_name' => 'admin',
'parent' => 'entity.user.admin_form',
];
$links['entity.entity_view_display.user.default_'] = [
'title' => t('Manage display'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.entity_view_display.user.default',
'menu_name' => 'admin',
'parent' => 'entity.user.admin_form',
@@ -231,6 +261,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
foreach (user_roles() as $role) {
$links['entity.user_role.edit_form.' . $role->id()] = [
'title' => t($role->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.user_role.collection',
@@ -238,6 +269,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
$links['entity.user_role.edit_permissions_form.' . $role->id()] = [
'title' => t('Edit permissions'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.edit_permissions_form',
'menu_name' => 'admin',
'parent' => 'entity.user_role.edit_form.' . $role->id(),
@@ -245,6 +277,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
$links['entity.user_role.delete_form.' . $role->id()] = [
'title' => t('Delete'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.delete_form',
'menu_name' => 'admin',
'parent' => 'entity.user_role.edit_form.' . $role->id(),
@@ -253,6 +286,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('devel')) {
$links['entity.user_role.devel_load.' . $role->id()] = [
'title' => t('Devel'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.user_role.devel_load',
'menu_name' => 'admin',
'parent' => 'entity.user_role.edit_form.' . $role->id(),
@@ -266,6 +300,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
$links['admin_toolbar_tools.add_content']['parent'] = 'system.admin_content';
$links['node.type_add'] = [
'title' => t('Add content type'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'node.type_add',
'menu_name' => 'admin',
'parent' => 'entity.node_type.collection',
@@ -275,6 +310,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
foreach ($entityTypeManager->getStorage('node_type')->loadMultiple() as $type) {
$links['node.add.' . $type->id()] = [
'title' => t($type->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'node.add',
'parent' => 'admin_toolbar_tools.add_content',
'route_parameters' => ['node_type' => $type->id()],
@@ -285,12 +321,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('field_ui')) {
$links['field_ui.entity_form_mode_add'] = [
'title' => t('Add new form mode'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'field_ui.entity_form_mode_add',
'menu_name' => 'admin',
'parent' => 'entity.entity_form_mode.collection',
];
$links['field_ui.entity_view_mode_add'] = [
'title' => t('Add new view mode'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'field_ui.entity_view_mode_add',
'menu_name' => 'admin',
'parent' => 'entity.entity_view_mode.collection',
@@ -300,6 +338,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('taxonomy')) {
$links['entity.taxonomy_vocabulary.add_form'] = [
'title' => t('Add vocabulary'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.taxonomy_vocabulary.add_form',
'menu_name' => 'admin',
'parent' => 'entity.taxonomy_vocabulary.collection',
@@ -310,6 +349,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('menu_ui')) {
$links['entity.menu.add_form'] = [
'title' => t('Add menu'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.add_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.collection',
@@ -319,6 +359,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
foreach (menu_ui_get_menus() as $machine_name => $label) {
$links['entity.menu.edit_form.' . $machine_name] = [
'title' => t($label),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.edit_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.collection',
@@ -326,14 +367,16 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
$links['entity.menu.delete_form.' . $machine_name] = [
'title' => t('Delete'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.delete_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.edit_form.' . $machine_name,
'route_parameters' => ['menu' => $machine_name],
];
if ($moduleHandler->moduleExists('devel')) {
if ($moduleHandler->moduleExists('devel') && $routeExists('entity.menu.devel_load')) {
$links['entity.menu.devel_load.' . $machine_name] = [
'title' => t('Devel'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.devel_load',
'menu_name' => 'admin',
'parent' => 'entity.menu.edit_form.' . $machine_name,
@@ -342,6 +385,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
}
$links['entity.menu.add_link_form.' . $machine_name] = [
'title' => t('Add link'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.menu.add_link_form',
'menu_name' => 'admin',
'parent' => 'entity.menu.edit_form.' . $machine_name,
@@ -354,6 +398,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('block_content')) {
$links['block_content.add_page'] = [
'title' => t('Add custom block'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'block_content.add_page',
'menu_name' => 'admin',
'parent' => 'block.admin_display',
@@ -361,12 +406,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
$links['entity.block_content.collection'] = [
'title' => t('Custom block library'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.block_content.collection',
'menu_name' => 'admin',
'parent' => 'block.admin_display',
];
$links['entity.block_content_type.collection'] = [
'title' => t('Types'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.block_content_type.collection',
'menu_name' => 'admin',
'parent' => 'block.admin_display',
@@ -377,6 +424,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('contact')) {
$links['contact.form_add'] = [
'title' => t('Add contact form'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'contact.form_add',
'menu_name' => 'admin',
'parent' => 'entity.contact_form.collection',
@@ -388,12 +436,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('update')) {
$links['update.module_update'] = [
'title' => t('Update'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.module_update',
'menu_name' => 'admin',
'parent' => 'system.modules_list',
];
$links['update.module_install'] = [
'title' => t('Install new module'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.module_install',
'menu_name' => 'admin',
'parent' => 'system.modules_list',
@@ -404,6 +454,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('devel')) {
$links['admin_development'] = [
'title' => t('Development'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'system.admin_config_development',
'menu_name' => 'admin',
'parent' => 'admin_toolbar_tools.help',
@@ -411,6 +462,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
$links['admin_toolbar_tools.devel.admin_settings'] = [
'title' => t('Devel settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.admin_settings',
'menu_name' => 'admin',
'parent' => 'admin_development',
@@ -419,6 +471,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('webprofiler')) {
$links['admin_toolbar_tools.devel.webprofiler'] = [
'title' => t('Web Profiler settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'webprofiler.settings',
'menu_name' => 'admin',
'parent' => 'admin_development',
@@ -426,53 +479,55 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
}
$links['admin_toolbar_tools.devel.configs_list'] = [
'title' => t('Config editor'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.configs_list',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.reinstall'] = [
'title' => t('Reinstall modules'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.reinstall',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.menu_rebuild'] = [
'title' => t('Rebuild menu'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.menu_rebuild',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.state_system_page'] = [
'title' => t('State editor'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.state_system_page',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.theme_registry'] = [
'title' => t('Theme registry'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.theme_registry',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.entity_info_page'] = [
'title' => t('Entity Info'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.entity_info_page',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.execute_php'] = [
'title' => t('Execute PHP Code'),
'route_name' => 'devel.execute_php',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.session'] = [
'title' => t('Session viewer'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.session',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
$links['admin_toolbar_tools.devel.elements_page'] = [
'title' => t('Form API field types'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.elements_page',
'menu_name' => 'admin',
'parent' => 'admin_development',
@@ -480,16 +535,35 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
// Menu link for the Toolbar module.
$links['admin_toolbar_tools.toolbar.settings'] = [
'title' => t('Toolbar settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.toolbar.settings_form',
'menu_name' => 'admin',
'parent' => 'devel.admin_settings',
];
}
// If module Devel PHP is enabled.
if ($moduleHandler->moduleExists('devel_php') && $routeExists('devel_php.execute_php')) {
$links['admin_toolbar_tools.devel_php.execute_php'] = [
'title' => t('Execute PHP Code'),
'route_name' => 'devel_php.execute_php',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
}
elseif ($moduleHandler->moduleExists('devel') && $routeExists('devel.execute_php')) {
$links['admin_toolbar_tools.devel.execute_php'] = [
'title' => t('Execute PHP Code'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'devel.execute_php',
'menu_name' => 'admin',
'parent' => 'admin_development',
];
}
// If module Views Ui enabled.
if ($moduleHandler->moduleExists('views_ui')) {
$links['admin_toolbar_tools.views_ui.add'] = [
'title' => t('Add new view'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'views_ui.add',
'menu_name' => 'admin',
'parent' => 'entity.view.collection',
@@ -497,6 +571,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
$links['admin_toolbar_tools.views_ui.field_list'] = [
'title' => t('Used in views'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'views_ui.reports_fields',
'menu_name' => 'admin',
'parent' => 'entity.field_storage_config.collection',
@@ -505,6 +580,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
$links['admin_toolbar_tools.system.theme_settings'] = [
'title' => t('Settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'system.theme_settings',
'menu_name' => 'admin',
'parent' => 'system.themes_page',
@@ -513,6 +589,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('webprofiler')) {
$links['admin_toolbar_tools.devel.webprofiler'] = [
'title' => t('Webprofiler settings'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'webprofiler.settings',
'menu_name' => 'admin',
'parent' => 'admin_development',
@@ -522,12 +599,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('update')) {
$links['update.theme_install_'] = [
'title' => t('Install new theme'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.theme_install',
'menu_name' => 'admin',
'parent' => 'system.themes_page',
];
$links['update.theme_update_'] = [
'title' => t('Update'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'update.theme_update',
'menu_name' => 'admin',
'parent' => 'system.themes_page',
@@ -537,6 +616,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
foreach ($installed_themes as $key_theme => $label_theme) {
$links['system.theme_settings_theme.' . $key_theme] = [
'title' => t($label_theme),
'provider' => 'admin_toolbar_tools',
'route_name' => 'system.theme_settings_theme',
'menu_name' => 'admin',
'parent' => 'system.theme_settings_',
@@ -551,6 +631,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('language')) {
$links['admin_toolbar_tools.language.negotiation'] = [
'title' => t('Detection and selection'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'language.negotiation',
'menu_name' => 'admin',
'parent' => 'entity.configurable_language.collection',
@@ -561,14 +642,16 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('media')) {
$links['admin_toolbar_tools.add_media'] = [
'title' => t('Add media'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.media.add_page',
'menu_name' => 'admin',
'parent' => 'system.admin_content',
];
// Add node links for each media type.
foreach (\Drupal::entityTypeManager()->getStorage('media_type')->loadMultiple() as $type) {
foreach ($entityTypeManager->getStorage('media_type')->loadMultiple() as $type) {
$links['media.add.' . $type->id()] = [
'title' => t($type->label()),
'provider' => 'admin_toolbar_tools',
'route_name' => 'entity.media.add_form',
'parent' => 'admin_toolbar_tools.add_media',
'route_parameters' => ['media_type' => $type->id()],
@@ -580,6 +663,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
if ($moduleHandler->moduleExists('config')) {
$links['admin_toolbar_tools.config.import'] = [
'title' => t('Import'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'config.import_full',
'menu_name' => 'admin',
'parent' => 'config.sync',
@@ -587,12 +671,15 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
];
$links['admin_toolbar_tools.config.export'] = [
'title' => t('Export'),
'provider' => 'admin_toolbar_tools',
'route_name' => 'config.export_full',
'menu_name' => 'admin',
'parent' => 'config.sync',
'weight' => 2,
];
}
$languageManager->setConfigOverrideLanguage($config_override_language);
}
/**
@@ -613,20 +700,3 @@ function admin_toolbar_tools_installed_themes() {
return $themes_installed;
}
/**
* 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 admin_toolbar_tools_get_links($entity_type_id) {
$entity = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
// Get all links related to entity.
$links = $entity->getLinkTemplates();
return $links;
}
@@ -34,6 +34,6 @@
},
"require": {
"drupal/admin_toolbar": "^1",
"drupal/core": "~8.3"
"drupal/core": "~8.5"
}
}
@@ -162,8 +162,8 @@ class ToolbarController extends ControllerBase {
* Flushes all caches.
*/
public function flushAll() {
$this->messenger()->addMessage($this->t('All caches cleared.'));
drupal_flush_all_caches();
drupal_set_message($this->t('All caches cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -173,7 +173,7 @@ class ToolbarController extends ControllerBase {
public function flushJsCss() {
$this->state()
->set('system.css_js_query_string', base_convert($this->time->getCurrentTime(), 10, 36));
drupal_set_message($this->t('CSS and JavaScript cache cleared.'));
$this->messenger()->addMessage($this->t('CSS and JavaScript cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -182,7 +182,7 @@ class ToolbarController extends ControllerBase {
*/
public function flushPlugins() {
$this->pluginCacheClearer->clearCachedDefinitions();
drupal_set_message($this->t('Plugins cache cleared.'));
$this->messenger()->addMessage($this->t('Plugins cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -191,7 +191,7 @@ class ToolbarController extends ControllerBase {
*/
public function flushStatic() {
drupal_static_reset();
drupal_set_message($this->t('Static cache cleared.'));
$this->messenger()->addMessage($this->t('Static cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -204,7 +204,7 @@ class ToolbarController extends ControllerBase {
$this->contextualLinkManager->clearCachedDefinitions();
$this->localTaskLinkManager->clearCachedDefinitions();
$this->localActionLinkManager->clearCachedDefinitions();
drupal_set_message($this->t('Routing and links cache cleared.'));
$this->messenger()->addMessage($this->t('Routing and links cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -213,7 +213,7 @@ class ToolbarController extends ControllerBase {
*/
public function flushViews() {
views_invalidate_cache();
drupal_set_message($this->t('Views cache cleared.'));
$this->messenger()->addMessage($this->t('Views cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -224,7 +224,7 @@ class ToolbarController extends ControllerBase {
// @todo Update once Drupal 8.6 will be released.
// @see https://www.drupal.org/node/2908461
PhpStorageFactory::get('twig')->deleteAll();
drupal_set_message($this->t('Twig cache cleared.'));
$this->messenger()->addMessage($this->t('Twig cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -233,7 +233,7 @@ class ToolbarController extends ControllerBase {
*/
public function runCron() {
$this->cron->run();
drupal_set_message($this->t('Cron ran successfully.'));
$this->messenger()->addMessage($this->t('Cron ran successfully.'));
return new RedirectResponse($this->reloadPage());
}
@@ -242,7 +242,7 @@ class ToolbarController extends ControllerBase {
*/
public function cacheRender() {
$this->cacheRender->invalidateAll();
drupal_set_message($this->t('Render cache cleared.'));
$this->messenger()->addMessage($this->t('Render cache cleared.'));
return new RedirectResponse($this->reloadPage());
}
@@ -5,8 +5,8 @@ description: 'Provides basic revert and update functionality for other modules'
dependencies:
- drupal:config
# Information added by Drupal.org packaging script on 2017-12-05
version: '8.x-1.5'
# Information added by Drupal.org packaging script on 2018-12-17
version: '8.x-1.6'
core: '8.x'
project: 'config_update'
datestamp: 1512514387
datestamp: 1545090489
@@ -285,8 +285,13 @@ function drush_config_update_ui_config_revert($name) {
// The revert command needs the type and the unprefixed name.
$type = $lister->getTypeNameByConfigName($name);
// The lister gives NULL if simple configuration, but the reverter expects
// 'system.simple' so we convert it.
if ($type === NULL) {
$type = 'system.simple';
}
$shortname = $name;
if ($type && $type != 'system.simple') {
if ($type != 'system.simple') {
$definition = $manager->getDefinition($type);
$prefix = $definition->getConfigPrefix() . '.';
if (strpos($name, $prefix) === 0) {
@@ -7,8 +7,8 @@ dependencies:
- config_update:config_update
- drupal:config
# Information added by Drupal.org packaging script on 2017-12-05
version: '8.x-1.5'
# Information added by Drupal.org packaging script on 2018-12-17
version: '8.x-1.6'
core: '8.x'
project: 'config_update'
datestamp: 1512514387
datestamp: 1545090489
@@ -35,3 +35,20 @@ function config_update_ui_help($route_name, RouteMatchInterface $route_match) {
return '<p>' . t('Most configuration is organized into a hierarchy of settings; at a minimum, it is a one-level hierarchy where each setting has a name and a value, and the hierarchy comes in when some of the settings have multiple components.') . '</p><p>' . t('Configuration items are normalized and formatted before computing differences. The normalization step alphabetizes the components at each level of the hierarchy, and removes a few components whose differences should be ignored, such as the UUID. The formatting step shows the full hierarchy of each configuration value with :: separators for the hierarchy levels, and a : separator between the lowest-level setting name and the value, so that in a line-by-line diff you can always see which values are actually different. Green lines with + signs have been added, and yellow lines with - signs have been removed.') . '</p><p>' . t('Note that differences are considering the base configuration, without overrides from your settings.php file, or translations.') . '</p>';
}
}
/**
* Implements hook_menu_links_discovered_alter().
*/
function config_update_ui_menu_links_discovered_alter(&$links) {
if (\Drupal::moduleHandler()->moduleExists('admin_toolbar_tools')) {
// Add the Updates report link to the Tools menu, but only if the
// Admin Toolbar Extra Tools module is present.
$links['admin_toolbar_tools.config.update'] = [
'title' => t('Updates report'),
'route_name' => 'config_update_ui.report',
'menu_name' => 'admin',
'parent' => 'config.sync',
'weight' => 3,
];
}
}
@@ -137,7 +137,7 @@ class ConfigDeleteConfirmForm extends ConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->configRevert->delete($this->type, $this->name);
drupal_set_message($this->t('The configuration was deleted.'));
$this->messenger()->addMessage($this->t('The configuration %item has been deleted.', ['%item' => $this->name]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
@@ -139,7 +139,7 @@ class ConfigImportConfirmForm extends ConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->configRevert->import($this->type, $this->name);
drupal_set_message($this->t('The configuration was imported from its source.'));
$this->messenger()->addMessage($this->t('The configuration %item has been imported from its source.', ['%item' => $this->name]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
@@ -139,7 +139,7 @@ class ConfigRevertConfirmForm extends ConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->configRevert->revert($this->type, $this->name);
drupal_set_message($this->t('The configuration was reverted to its source.'));
$this->messenger()->addMessage($this->t('The configuration %item has been reverted to its source.', ['%item' => $this->name]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
@@ -1,15 +1,15 @@
<?php
namespace Drupal\config_update_ui\Tests;
namespace Drupal\Tests\config_update_ui\Functional;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Verify config reports, reverts, and diffs with profile overrides.
*
* @group config
* @group config_update
*/
class ConfigProfileOverridesTest extends WebTestBase {
class ConfigProfileOverridesTest extends BrowserTestBase {
/**
* Use the Standard profile, so that there are profile config overrides.
@@ -43,7 +43,7 @@ class ConfigProfileOverridesTest extends WebTestBase {
parent::setUp();
// Create user and log in.
$this->adminUser = $this->drupalCreateUser([
$this->adminUser = $this->createUser([
'access administration pages',
'administer themes',
'view config updates report',
@@ -68,7 +68,8 @@ class ConfigProfileOverridesTest extends WebTestBase {
// that system.theme is not shown (it should not be missing, or added,
// or overridden).
$this->drupalGet('admin/config/development/configuration/report/type/system.simple');
$this->assertNoRaw('system.theme');
$session = $this->assertSession();
$session->responseNotContains('system.theme');
// Go to the Appearance page and change the theme to whatever is currently
// disabled. Return to the report and verify that system.theme is there,
@@ -76,22 +77,25 @@ class ConfigProfileOverridesTest extends WebTestBase {
$this->drupalGet('admin/appearance');
$this->clickLink('Install and set as default');
$this->drupalGet('admin/config/development/configuration/report/type/system.simple');
$this->assertText('system.theme');
$session = $this->assertSession();
$session->pageTextContains('system.theme');
// Look at the differences for system.theme and verify it's against
// the standard profile version, not default version. The line for
// default should show bartik as the source; if it's against the system
// version, the word bartik would not be there.
$this->drupalGet('admin/config/development/configuration/report/diff/system.simple/system.theme');
$this->assertText('bartik');
$session = $this->assertSession();
$session->pageTextContains('bartik');
// Revert and verify that it reverted to the profile version, not the
// system module version.
$this->drupalGet('admin/config/development/configuration/report/revert/system.simple/system.theme');
$this->drupalPostForm(NULL, [], 'Revert');
$this->drupalGet('admin/config/development/configuration/single/export/system.simple/system.theme');
$this->assertText('admin: seven');
$this->assertText('default: bartik');
$session = $this->assertSession();
$session->pageTextContains('admin: seven');
$session->pageTextContains('default: bartik');
}
}
@@ -1,15 +1,15 @@
<?php
namespace Drupal\config_update_ui\Tests;
namespace Drupal\Tests\config_update_ui\Functional;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Verify the config revert report and its links.
*
* @group config
* @group config_update
*/
class ConfigUpdateTest extends WebTestBase {
class ConfigUpdateTest extends BrowserTestBase {
/**
* Modules to enable.
@@ -46,7 +46,7 @@ class ConfigUpdateTest extends WebTestBase {
parent::setUp();
// Create user and log in.
$this->adminUser = $this->drupalCreateUser([
$this->adminUser = $this->createUser([
'access administration pages',
'administer search',
'view config updates report',
@@ -60,8 +60,8 @@ class ConfigUpdateTest extends WebTestBase {
$this->drupalLogin($this->adminUser);
// Make sure local tasks and page title are showing.
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
$this->placeBlock('local_tasks_block');
$this->placeBlock('page_title_block');
// Load the Drush include file so that its functions can be tested, plus
// the Drush testing include file.
@@ -103,13 +103,15 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertReport('Testing profile', [], [], [], $inactive, ['added']);
// The locale.settings line should show that the Testing profile is the
// provider.
$this->assertText('Testing profile');
$session = $this->assertSession();
$session->pageTextContains('Testing profile');
$this->assertDrushReports('profile', '', [], [], [], array_keys($inactive));
// Verify that the user search page cannot be imported (because it already
// exists).
$this->drupalGet('admin/config/development/configuration/report/import/search_page/user_search');
$this->assertResponse(404);
$session = $this->assertSession();
$session->statusCodeEquals(404);
// Delete the user search page from the search UI and verify report for
// both the search page config type and user module.
@@ -121,7 +123,8 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertReport('Search page', [], [], [], $inactive);
// The search.page.user_search line should show that the User module is the
// provider.
$this->assertText('User module');
$session = $this->assertSession();
$session->pageTextContains('User module');
$this->assertDrushReports('type', 'search_page', [], [], [], array_keys($inactive));
$this->drupalGet('admin/config/development/configuration/report/module/user');
@@ -138,17 +141,20 @@ class ConfigUpdateTest extends WebTestBase {
// Verify that the user search page cannot be reverted (because it does
// not already exist).
$this->drupalGet('admin/config/development/configuration/report/revert/search_page/user_search');
$this->assertResponse(404);
$session = $this->assertSession();
$session->statusCodeEquals(404);
// Verify that the delete URL doesn't work either.
$this->drupalGet('admin/config/development/configuration/report/delete/search_page/user_search');
$this->assertResponse(404);
$session = $this->assertSession();
$session->statusCodeEquals(404);
// Use the import link to get it back. Do this from the search page
// report to make sure we are importing the right config.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Import from source');
$this->drupalPostForm(NULL, [], 'Import');
$this->assertText('The configuration was imported');
$session = $this->assertSession();
$session->pageTextContains('has been imported');
$this->assertNoReport();
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
@@ -156,8 +162,9 @@ class ConfigUpdateTest extends WebTestBase {
// 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:');
$session = $this->assertSession();
$session->pageTextContains('id: user_search');
$session->pageTextNotContains('default_config_hash:');
// Test importing again, this time using the Drush import command.
$this->drupalGet('admin/config/search/pages');
@@ -184,10 +191,11 @@ class ConfigUpdateTest extends WebTestBase {
// Test the show differences link.
$this->clickLink('Show differences');
$this->assertText('Content');
$this->assertText('New label');
$this->assertText('node');
$this->assertText('new_path');
$session = $this->assertSession();
$session->pageTextContains('Content');
$session->pageTextContains('New label');
$session->pageTextContains('node');
$session->pageTextContains('new_path');
// Test the show differences Drush command.
$output = drush_config_update_ui_config_diff('search.page.node_search');
@@ -203,36 +211,39 @@ class ConfigUpdateTest extends WebTestBase {
// Test the export link.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Export');
$this->assertText('Here is your configuration:');
$this->assertText('id: node_search');
$this->assertText('New label');
$this->assertText('path: new_path');
$this->assertText('search.page.node_search.yml');
$session = $this->assertSession();
$session->pageTextContains('Here is your configuration:');
$session->pageTextContains('id: node_search');
$session->pageTextContains('New label');
$session->pageTextContains('path: new_path');
$session->pageTextContains('search.page.node_search.yml');
// Grab the uuid and hash lines for the next test.
$text = $this->getTextContent();
// Grab the uuid and hash lines from the exported config for the next test.
$text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml());
$matches = [];
preg_match('|^.*uuid:.*$|m', $text, $matches);
$uuid_line = $matches[0];
$uuid_line = trim($matches[0]);
preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
$hash_line = $matches[0];
$hash_line = trim($matches[0]);
// Test reverting.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Revert to source');
$this->assertText('Are you sure you want to revert');
$this->assertText('Search page');
$this->assertText('node_search');
$this->assertText('Customizations will be lost. This action cannot be undone');
$session = $this->assertSession();
$session->pageTextContains('Are you sure you want to revert');
$session->pageTextContains('Search page');
$session->pageTextContains('node_search');
$session->pageTextContains('Customizations will be lost. This action cannot be undone');
$this->drupalPostForm(NULL, [], 'Revert');
$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);
$session = $this->assertSession();
$session->pageTextContains('id: node_search');
$session->pageTextContains($uuid_line);
$session->pageTextContains($hash_line);
// Test reverting again, this time using Drush single revert command.
$this->drupalGet('admin/config/search/pages');
@@ -278,19 +289,22 @@ class ConfigUpdateTest extends WebTestBase {
// Test the export link.
$this->clickLink('Export');
$this->assertText('Here is your configuration:');
$this->assertText('id: test');
$this->assertText('label: test');
$this->assertText('path: test');
$this->assertText('search.page.test.yml');
$session = $this->assertSession();
$session->pageTextContains('Here is your configuration:');
$session->pageTextContains('id: test');
$session->pageTextContains('label: test');
$session->pageTextContains('path: test');
$session->pageTextContains('search.page.test.yml');
// Test the delete link.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Delete');
$this->assertText('Are you sure');
$this->assertText('cannot be undone');
$session = $this->assertSession();
$session->pageTextContains('Are you sure');
$session->pageTextContains('cannot be undone');
$this->drupalPostForm(NULL, [], 'Delete');
$this->assertText('The configuration was deleted');
$session = $this->assertSession();
$session->pageTextContains('has been deleted');
// And verify the report again.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
@@ -306,18 +320,20 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertReport('Search module', [], [], $changed, [], ['added']);
$this->clickLink('Show differences');
$this->assertText('Config difference for Simple configuration search.settings');
$this->assertText('index::minimum_word_size');
$this->assertText('4');
$session = $this->assertSession();
$session->pageTextContains('Config difference for Simple configuration search.settings');
$session->pageTextContains('index::minimum_word_size');
$session->pageTextContains('4');
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->clickLink('Export');
$this->assertText('minimum_word_size: 4');
$session = $this->assertSession();
$session->pageTextContains('minimum_word_size: 4');
// Grab the hash line for the next test.
$text = $this->getTextContent();
$text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml());
$matches = [];
preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
$hash_line = $matches[0];
$hash_line = trim($matches[0]);
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->clickLink('Revert to source');
@@ -325,7 +341,8 @@ class ConfigUpdateTest extends WebTestBase {
// 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);
$session = $this->assertSession();
$session->pageTextContains($hash_line);
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->assertReport('Search module', [], [], [], [], ['added']);
@@ -338,6 +355,10 @@ class ConfigUpdateTest extends WebTestBase {
$changed = ['filter.format.plain_text' => 'New label'];
$this->drupalGet('admin/config/development/configuration/report/type/filter_format');
$this->assertReport('Text format', [], [], $changed, []);
// Verify that we can revert non-entity configuration in Drush. Issue:
// https://www.drupal.org/project/config_update/issues/2935395
drush_config_update_ui_config_revert('system.date');
}
/**
@@ -359,62 +380,63 @@ class ConfigUpdateTest extends WebTestBase {
* Array of report sections to skip checking.
*/
protected function assertReport($title, array $missing, array $added, array $changed, array $inactive, array $skip = []) {
$this->assertText('Configuration updates report for ' . $title);
$this->assertText('Generate new report');
$session = $this->assertSession();
$session->pageTextContains('Configuration updates report for ' . $title);
$session->pageTextContains('Generate new report');
if (!in_array('missing', $skip)) {
$this->assertText('Missing configuration items');
$session->pageTextContains('Missing configuration items');
if (count($missing)) {
foreach ($missing as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: all provided configuration items are in your active configuration.');
$session->pageTextNotContains('None: all provided configuration items are in your active configuration.');
}
else {
$this->assertText('None: all provided configuration items are in your active configuration.');
$session->pageTextContains('None: all provided configuration items are in your active configuration.');
}
}
if (!in_array('inactive', $skip)) {
$this->assertText('Inactive optional items');
$session->pageTextContains('Inactive optional items');
if (count($inactive)) {
foreach ($inactive as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: all optional configuration items are in your active configuration.');
$session->pageTextNotContains('None: all optional configuration items are in your active configuration.');
}
else {
$this->assertText('None: all optional configuration items are in your active configuration.');
$session->pageTextContains('None: all optional configuration items are in your active configuration.');
}
}
if (!in_array('added', $skip)) {
$this->assertText('Added configuration items');
$session->pageTextContains('Added configuration items');
if (count($added)) {
foreach ($added as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: all active configuration items of this type were provided by modules, themes, or install profile.');
$session->pageTextNotContains('None: all active configuration items of this type were provided by modules, themes, or install profile.');
}
else {
$this->assertText('None: all active configuration items of this type were provided by modules, themes, or install profile.');
$session->pageTextContains('None: all active configuration items of this type were provided by modules, themes, or install profile.');
}
}
if (!in_array('changed', $skip)) {
$this->assertText('Changed configuration items');
$session->pageTextContains('Changed configuration items');
if (count($changed)) {
foreach ($changed as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: no active configuration items differ from their current provided versions.');
$session->pageTextNotContains('None: no active configuration items differ from their current provided versions.');
}
else {
$this->assertText('None: no active configuration items differ from their current provided versions.');
$session->pageTextContains('None: no active configuration items differ from their current provided versions.');
}
}
}
@@ -440,7 +462,7 @@ class ConfigUpdateTest extends WebTestBase {
protected function assertDrushReports($type, $name, array $missing, array $added, array $changed, array $inactive, array $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');
$this->assertEquals(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");
@@ -450,7 +472,7 @@ class ConfigUpdateTest extends WebTestBase {
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');
$this->assertEquals(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");
@@ -460,7 +482,7 @@ class ConfigUpdateTest extends WebTestBase {
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');
$this->assertEquals(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");
@@ -470,7 +492,7 @@ class ConfigUpdateTest extends WebTestBase {
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');
$this->assertEquals(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");
@@ -485,37 +507,38 @@ class ConfigUpdateTest extends WebTestBase {
* Assumes you are already on the report form page.
*/
protected function assertNoReport() {
$this->assertText('Report type');
$this->assertText('Full report');
$this->assertText('Single configuration type');
$this->assertText('Single module');
$this->assertText('Single theme');
$this->assertText('Installation profile');
$this->assertText('Updates report');
$this->assertNoText('Missing configuration items');
$this->assertNoText('Added configuration items');
$this->assertNoText('Changed configuration items');
$this->assertNoText('Unchanged configuration items');
$session = $this->assertSession();
$session->pageTextContains('Report type');
$session->pageTextContains('Full report');
$session->pageTextContains('Single configuration type');
$session->pageTextContains('Single module');
$session->pageTextContains('Single theme');
$session->pageTextContains('Installation profile');
$session->pageTextContains('Updates report');
$session->pageTextNotContains('Missing configuration items');
$session->pageTextNotContains('Added configuration items');
$session->pageTextNotContains('Changed configuration items');
$session->pageTextNotContains('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');
$session->linkExists('Search');
$session->linkExists('Field');
$session->linkNotExists('Configuration Update Base');
$session->linkNotExists('Configuration Update Reports');
// Themes.
$this->assertNoLink('Stark');
$this->assertNoLink('Classy');
$session->linkNotExists('Stark');
$session->linkNotExists('Classy');
// Profiles.
$this->assertLink('Testing');
$session->linkExists('Testing');
// Configuration types.
$this->assertLink('Everything');
$this->assertLink('Simple configuration');
$this->assertLink('Search page');
$session->linkExists('Everything');
$session->linkExists('Simple configuration');
$session->linkExists('Search page');
}
}
@@ -0,0 +1,37 @@
# This is the DrupalCI testbot build file for Configuration Update Manager.
# Learn to make one for your own drupal.org project:
# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing
build:
assessment:
validate_codebase:
phplint:
phpcs:
# phpcs will use core's specified version of Coder.
sniff-all-files: true
halt-on-fail: true
testing:
# run_tests task is executed several times in order of performance speeds.
# halt-on-fail can be set on the run_tests tasks in order to fail fast.
# suppress-deprecations is false in order to be alerted to usages of
# deprecated code.
run_tests.phpunit:
types: 'PHPUnit-Unit'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
run_tests.kernel:
types: 'PHPUnit-Kernel'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
run_tests.functional:
types: 'PHPUnit-Functional'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
run_tests.javascript:
concurrency: 15
types: 'PHPUnit-FunctionalJavascript'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
@@ -5,6 +5,7 @@ namespace Drupal\config_update;
use Drupal\Component\Diff\Diff;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Serialization\Yaml;
/**
* Provides methods related to config differences.
@@ -14,7 +15,7 @@ class ConfigDiffer implements ConfigDiffInterface {
use StringTranslationTrait;
/**
* List of elements to ignore when comparing config.
* List of elements to ignore on top level when comparing config.
*
* @var string[]
*
@@ -46,7 +47,7 @@ class ConfigDiffer implements ConfigDiffInterface {
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* String translation service.
* @param string[] $ignore
* Config components to ignore.
* Config components to ignore at the top level.
* @param string $hierarchy_prefix
* Prefix to use in diffs for array hierarchy.
* @param string $value_prefix
@@ -62,9 +63,9 @@ class ConfigDiffer implements ConfigDiffInterface {
/**
* Normalizes config for comparison.
*
* Recursively removes elements in the ignore list from configuration,
* as well as empty array values, and sorts at each level by array key, so
* that config from different storage can be compared meaningfully.
* Removes elements in the ignore list from the top level of configuration,
* and at each level of the array, removes empty arrays and sorts by array
* key, so that config from different storage can be compared meaningfully.
*
* @param array|null $config
* Configuration array to normalize.
@@ -80,27 +81,40 @@ class ConfigDiffer implements ConfigDiffInterface {
return [];
}
// Remove "ignore" elements.
// Remove "ignore" elements, only at the top level.
foreach ($this->ignore as $element) {
unset($config[$element]);
}
// Recursively normalize remaining elements, if they are arrays.
foreach ($config as $key => $value) {
// Recursively normalize and return.
return $this->normalizeArray($config);
}
/**
* Recursively sorts an array by key, and removes empty arrays.
*
* @param array $array
* An array to normalize.
*
* @return array
* An array that is sorted by key, at each level of the array, with empty
* arrays removed.
*/
protected function normalizeArray(array $array) {
foreach ($array as $key => $value) {
if (is_array($value)) {
$new = $this->normalize($value);
$new = $this->normalizeArray($value);
if (count($new)) {
$config[$key] = $new;
$array[$key] = $new;
}
else {
unset($config[$key]);
unset($array[$key]);
}
}
}
// Sort and return.
ksort($config);
return $config;
ksort($array);
return $array;
}
/**
@@ -157,11 +171,8 @@ class ConfigDiffer implements ConfigDiffInterface {
$lines[] = $line;
}
}
elseif (is_null($value)) {
$lines[] = $section_prefix . $this->valuePrefix . $this->t('(NULL)');
}
else {
$lines[] = $section_prefix . $this->valuePrefix . $value;
$lines[] = $section_prefix . $this->valuePrefix . Yaml::encode($value);
}
}
@@ -89,7 +89,7 @@ class ConfigLister implements ConfigListInterface {
// Calculate and return the list.
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
if ($definition->entityClassImplements('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$this->definitions[$entity_type] = $definition;
$prefix = $definition->getConfigPrefix();
$this->typesByPrefix[$prefix] = $entity_type;
@@ -72,7 +72,7 @@ interface ConfigRevertInterface {
* The name of the config item, without the prefix.
*
* @return array
* The configuration value.
* The configuration value, or FALSE if it is not found.
*/
public function getFromActive($type, $name);
@@ -87,11 +87,15 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* {@inheritdoc}
*/
public function import($type, $name) {
// Read the config from the file.
// Read the config from the file. Note: Do not call getFromExtension() here
// because we need $full_name below.
$full_name = $this->getFullName($type, $name);
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
$value = FALSE;
if ($full_name) {
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
}
}
if (!$value) {
return FALSE;
@@ -118,36 +122,44 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* {@inheritdoc}
*/
public function revert($type, $name) {
// Read the config from the file.
// Read the config from the file. Note: Do not call getFromExtension() here
// because we need $full_name below.
$value = FALSE;
$full_name = $this->getFullName($type, $name);
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
if ($full_name) {
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
}
}
if (!$value) {
return FALSE;
}
// Make sure the configuration exists currently in active storage.
if (!$this->activeConfigStorage->read($full_name)) {
return FALSE;
}
// Load the current config and replace the value, retaining the config
// hash (which is part of the _core config key's value).
if ($type == 'system.simple') {
// 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();
$config
->setData($value)
->set('_core', $core)
->save();
}
else {
// 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);
$core = $entity->get('_core');
$entity = $entity_storage->updateFromStorageRecord($entity, $value);
$entity->set('_core', $core);
$entity->save();
}
@@ -162,11 +174,15 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* {@inheritdoc}
*/
public function delete($type, $name) {
$config = FALSE;
$full_name = $this->getFullName($type, $name);
if (!$full_name) {
return FALSE;
if ($full_name) {
// Make sure the configuration exists currently in active storage.
if (!$this->activeConfigStorage->read($full_name)) {
return FALSE;
}
$config = $this->configFactory->getEditable($full_name);
}
$config = $this->configFactory->getEditable($full_name);
if (!$config) {
return FALSE;
}
@@ -183,17 +199,23 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
*/
public function getFromActive($type, $name) {
$full_name = $this->getFullName($type, $name);
return $this->activeConfigStorage->read($full_name);
if ($full_name) {
return $this->activeConfigStorage->read($full_name);
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getFromExtension($type, $name) {
$value = FALSE;
$full_name = $this->getFullName($type, $name);
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
if ($full_name) {
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
}
}
return $value;
}
@@ -207,7 +229,7 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* The config name, without prefix.
*
* @return string
* The config item's full name.
* The config item's full name, or FALSE if there is an error.
*/
protected function getFullName($type, $name) {
if ($type == 'system.simple' || !$type) {
@@ -215,8 +237,13 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
}
$definition = $this->entityManager->getDefinition($type);
$prefix = $definition->getConfigPrefix() . '.';
return $prefix . $name;
if ($definition) {
$prefix = $definition->getConfigPrefix() . '.';
return $prefix . $name;
}
else {
return FALSE;
}
}
}
@@ -3,7 +3,6 @@
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigDiffer;
use Drupal\Tests\UnitTestCase;
/**
* Tests the \Drupal\config_update\ConfigDiffer class.
@@ -12,14 +11,7 @@ use Drupal\Tests\UnitTestCase;
*
* @coversDefaultClass \Drupal\config_update\ConfigDiffer
*/
class ConfigDifferTest extends UnitTestCase {
/**
* The mock translation object.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $stringTranslation;
class ConfigDifferTest extends ConfigUpdateUnitTestBase {
/**
* The config differ to test.
@@ -32,12 +24,7 @@ class ConfigDifferTest extends UnitTestCase {
* {@inheritdoc}
*/
protected function setUp() {
$this->stringTranslation = $this->getMockBuilder('Drupal\Core\StringTranslation\TranslationInterface')->getMock();
$this->stringTranslation
->method('t')
->will($this->returnArgument(0));
$this->configDiffer = new ConfigDiffer($this->stringTranslation);
$this->configDiffer = new ConfigDiffer($this->getTranslationMock());
}
/**
@@ -59,13 +46,14 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
];
return [
[$base, $base, TRUE],
// Add _core, omit uuid. Should match.
// Add _core, omit uuid at top level. Should match, as both are removed
// in normalization process.
[
$base,
[
@@ -75,27 +63,30 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
TRUE,
],
// Change order. Should match.
// Change order in top and deep level. Should match.
[
$base,
[
'a' => 'a',
'uuid' => 'bar',
'b' => 0,
'a' => 'a',
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
'd' => TRUE,
],
],
TRUE,
],
// Change order and add _core in deeper level. Should match.
// Add _core in deeper level. Should not match, as this is removed
// only at the top level during normalization.
[
$base,
[
@@ -103,12 +94,31 @@ class ConfigDifferTest extends UnitTestCase {
'a' => 'a',
'b' => 0,
'c' => [
'e' => FALSE,
'_core' => 'foo',
'_core' => 'do-not-use-this-key',
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
TRUE,
FALSE,
],
// Add uuid in deeper level. Should not match, as this is removed
// only at the top level during normalization.
[
$base,
[
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => FALSE,
'uuid' => 'important',
'empty' => [],
],
],
FALSE,
],
// Omit a component. Should not match.
@@ -120,6 +130,7 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
@@ -135,6 +146,7 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
'f' => 'f',
],
@@ -152,6 +164,24 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
],
// 0 should not match NULL.
[
$base,
[
'_core' => 'foo',
'uuid' => 'bar',
'a' => 'a',
'b' => NULL,
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
@@ -168,6 +198,7 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => 'e',
'empty' => [],
],
],
FALSE,
@@ -184,10 +215,29 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => 'd',
'e' => FALSE,
'empty' => [],
],
],
FALSE,
],
// Add an empty array at top, and remove at lower level. Should still
// match.
[
$base,
[
'_core' => 'foo',
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => FALSE,
],
'empty_two' => [],
],
TRUE,
],
];
}
@@ -201,6 +251,7 @@ class ConfigDifferTest extends UnitTestCase {
'id_to_remove' => 'test.remove.id',
'type' => 'old_type',
'true_value' => TRUE,
'null_value' => NULL,
'nested_array' => [
'flat_array' => [
'value2',
@@ -216,6 +267,7 @@ class ConfigDifferTest extends UnitTestCase {
'id' => 'test.config.id',
'type' => 'new_type',
'true_value' => FALSE,
'null_value' => FALSE,
'nested_array' => [
'flat_array' => [
'value2',
@@ -284,12 +336,14 @@ class ConfigDifferTest extends UnitTestCase {
'orig' => [
'nested_array::flat_array::1 : value1',
'nested_array::flat_array::2 : value3',
'true_value : 1',
'null_value : null',
'true_value : true',
'type : old_type',
],
'closing' => [
'nested_array::flat_array::1 : value3',
'true_value : ',
'null_value : false',
'true_value : false',
'type : new_type',
],
],
@@ -2,169 +2,73 @@
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigLister;
use Drupal\Tests\UnitTestCase;
/**
* Tests the \Drupal\config_update\ConfigLister class.
* Tests the \Drupal\config_update\ConfigListerWithProviders class.
*
* The methods from \Drupal\config_update\ConfigLister are also tested.
*
* @group config_update
*
* @coversDefaultClass \Drupal\config_update\ConfigLister
* @coversDefaultClass \Drupal\config_update\ConfigListerWithProviders
*/
class ConfigListerTest extends UnitTestCase {
class ConfigListerTest extends ConfigUpdateUnitTestBase {
/**
* The config lister to test.
*
* @var \Drupal\config_update\ConfigLister
* @var \Drupal\config_update\ConfigListerWithProviders
*/
protected $configLister;
/**
* The mocked entity definition information.
* List of configuration by provider in the mocks.
*
* @var string[]
* This is an array whose keys are provider names, and whose values are
* each an array containing the provider type, the name of one config item
* mocked to be in config/install, and one in config/optional.
*
* @var array
*/
protected $entityDefinitionInformation;
protected $configProviderList = [
'foo_module' => ['module', 'foo.barbaz.one', 'foo.barbaz.two'],
'foo_theme' => ['theme', 'foo.bar.one', 'foo.bar.two'],
'standard' => ['profile', 'baz.bar.one', 'baz.bar.two'],
];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->configLister = new ConfigLister($this->getEntityManagerMock(), $this->getConfigStorageMock('active'), $this->getConfigStorageMock('extension'), $this->getConfigStorageMock('optional'));
$lister = $this->getMockBuilder('Drupal\config_update\ConfigListerWithProviders')
->setConstructorArgs([
$this->getEntityManagerMock(),
$this->getConfigStorageMock('active'),
$this->getConfigStorageMock('extension'),
$this->getConfigStorageMock('optional'),
$this->getModuleHandlerMock(),
$this->getThemeHandlerMock(),
])
->setMethods(['listProvidedItems', 'getProfileName'])
->getMock();
$lister->method('getProfileName')
->willReturn('standard');
$map = [];
foreach ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// Map needs: [type, provider name, isOptional, [config items]].
$map[] = [$info[0], $provider, FALSE, [$info[1]]];
$map[] = [$info[0], $provider, TRUE, [$info[2]]];
}
$lister->method('listProvidedItems')
->will($this->returnValueMap($map));
$this->configLister = $lister;
}
/**
* Creates a mock entity manager for the test.
*/
protected function getEntityManagerMock() {
// Make a list of fake entity definitions. Make sure they are not sorted,
// to test that the methods sort them. Also make sure there are a couple
// with prefixes that are subsets of each other.
$this->entityDefinitionInformation = [
['prefix' => 'foo.bar', 'type' => 'foo'],
['prefix' => 'foo.barbaz', 'type' => 'bar'],
['prefix' => 'baz.foo', 'type' => 'baz'],
];
$definitions = [];
foreach ($this->entityDefinitionInformation as $info) {
$def = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityTypeInterface')->getMock();
$def
->expects($this->any())
->method('getConfigPrefix')
->willReturn($info['prefix']);
$def
->expects($this->any())
->method('isSubclassOf')
->willReturn(TRUE);
$def->getConfigPrefix();
$definitions[$info['type']] = $def;
}
$manager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')->getMock();
$manager
->method('getDefinitions')
->willReturn($definitions);
return($manager);
}
/**
* Creates a mock config storage object for the test.
*
* @param string $type
* Type of storage object to return: 'active', 'extension', or 'optional'.
*/
protected function getConfigStorageMock($type) {
if ($type == 'active') {
$storage = $this->getMockBuilder('Drupal\Core\Config\StorageInterface')->getMock();
// The only use of the read() method on active storage is
// with the core.extension config, to get the profile name.
$storage
->method('read')
->willReturn(['profile' => 'standard']);
$map = [
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.three']],
['foo.barbaz', ['foo.barbaz.four', 'foo.barbaz.five', 'foo.barbaz.six']],
['baz.foo'], [],
['',
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
],
];
$storage
->method('listAll')
->will($this->returnValueMap($map));
}
elseif ($type == 'extension') {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$storage
->method('getComponentNames')
->willReturn([
'foo.bar.one' => 'ignored',
'foo.bar.two' => 'ignored',
'foo.bar.seven' => 'ignored',
'foo.barnot.three' => 'ignored',
'something.else' => 'ignored',
]);
$map = [
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.seven']],
['baz.foo'], [],
['',
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.seven',
'foo.barbaz.four',
'foo.barnot.three',
'something.else',
],
],
];
$storage
->method('listAll')
->will($this->returnValueMap($map));
}
else {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$storage
->method('getComponentNames')
->willReturn([
'foo.barbaz.four' => 'ignored',
]);
$map = [
['foo.bar'], [],
['foo.barbaz', ['foo.barbaz.four']],
['', ['foo.barbaz.four']],
];
$storage
->method('listAll')
->will($this->returnValueMap($map));
}
return $storage;
}
/**
* @covers \Drupal\config_update\ConfigLister::listConfig
* @covers \Drupal\config_update\ConfigListerWithProviders::listConfig
* @dataProvider listConfigProvider
*/
public function testListConfig($a, $b, $expected) {
@@ -176,12 +80,8 @@ class ConfigListerTest extends UnitTestCase {
*/
public function listConfigProvider() {
return [
// Arguments are $list_type, $name.
// We cannot really test the extension types here, because they rely
// on the going out to the file system to find out what config objects
// are there. This is too complex to mock. It is tested in the tests for
// the report output in the config_update_ui module tests. Anyway, we
// can test the other types.
// Arguments are $list_type, $name, and return value is that list of
// configuration in active, extension, and optional storage.
['type', 'system.all',
[
[
@@ -220,11 +120,59 @@ class ConfigListerTest extends UnitTestCase {
],
],
['type', 'unknown.type', [[], [], []]],
['profile', 'dummy',
[
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
['baz.bar.one'],
['baz.bar.two'],
],
],
['module', 'foo_module',
[
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
['foo.barbaz.one'],
['foo.barbaz.two'],
],
],
['theme', 'foo_theme',
[
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
['foo.bar.one'],
['foo.bar.two'],
],
],
];
}
/**
* @covers \Drupal\config_update\ConfigLister::getType
* @covers \Drupal\config_update\ConfigListerWithProviders::getType
*/
public function testGetType() {
$return = $this->configLister->getType('not_in_list');
@@ -237,7 +185,7 @@ class ConfigListerTest extends UnitTestCase {
}
/**
* @covers \Drupal\config_update\ConfigLister::getTypeByPrefix
* @covers \Drupal\config_update\ConfigListerWithProviders::getTypeByPrefix
*/
public function testGetTypeByPrefix() {
$return = $this->configLister->getTypeByPrefix('not_in_list');
@@ -250,7 +198,7 @@ class ConfigListerTest extends UnitTestCase {
}
/**
* @covers \Drupal\config_update\ConfigLister::getTypeNameByConfigName
* @covers \Drupal\config_update\ConfigListerWithProviders::getTypeNameByConfigName
*/
public function testGetTypeNameByConfigName() {
$return = $this->configLister->getTypeNameByConfigName('not_in_list');
@@ -262,4 +210,84 @@ class ConfigListerTest extends UnitTestCase {
}
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::listTypes
*/
public function testListTypes() {
$return = $this->configLister->listTypes();
// Should return an array in sorted order, of just the config entities
// that $this->getEntityManagerMock() set up.
$expected = ['bar' => 'foo.barbaz', 'baz' => 'baz.foo', 'foo' => 'foo.bar'];
$this->assertEquals(array_keys($return), array_keys($expected));
foreach ($return as $key => $definition) {
$this->assertTrue($definition->entityClassImplements('Drupal\Core\Config\Entity\ConfigEntityInterface'));
$this->assertEquals($definition->getConfigPrefix(), $expected[$key]);
}
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::listProviders
*/
public function testListProviders() {
// This method's return value is not sorted in any particular way.
$return = $this->configLister->listProviders();
$expected = [];
foreach ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// Expected needs: key is item name, value is [type, provider name].
$expected[$info[1]] = [$info[0], $provider];
$expected[$info[2]] = [$info[0], $provider];
}
ksort($return);
ksort($expected);
$this->assertEquals($return, $expected);
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::getConfigProvider
* @dataProvider getConfigProviderProvider
*/
public function testGetConfigProvider($a, $expected) {
$this->assertEquals($expected, $this->configLister->getConfigProvider($a));
}
/**
* Data provider for self:testGetConfigProvider().
*/
public function getConfigProviderProvider() {
$values = [];
foreach ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// Values needs: [item, [type, provider name]].
$values[] = [$info[1], [$info[0], $provider]];
$values[] = [$info[2], [$info[0], $provider]];
}
$values[] = ['not.a.config.item', NULL];
return $values;
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::providerHasConfig
* @dataProvider providerHasConfigProvider
*/
public function testProviderHasConfig($a, $b, $expected) {
$this->assertEquals($expected, $this->configLister->providerHasConfig($a, $b));
}
/**
* Data provider for self:testProviderHasConfig().
*/
public function providerHasConfigProvider() {
$values = [];
foreach ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// Values needs: [type, provider name, TRUE] for valid providers,
// change the last to FALSE for invalid providers.
$values[] = [$info[0], $provider, TRUE];
$values[] = [$info[0], $provider . '_suffix', FALSE];
}
$values[] = ['invalid_type', 'foo_module', FALSE];
return $values;
}
}
@@ -0,0 +1,356 @@
<?php
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigReverter;
use Drupal\config_update\ConfigRevertInterface;
use Drupal\config_update\ConfigDeleteInterface;
/**
* Tests the \Drupal\config_update\ConfigReverter class.
*
* @group config_update
*
* @coversDefaultClass \Drupal\config_update\ConfigReverter
*/
class ConfigReverterTest extends ConfigUpdateUnitTestBase {
/**
* The config reverter to test.
*
* @var \Drupal\config_update\ConfigReverter
*/
protected $configReverter;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->configReverter = new ConfigReverter(
$this->getEntityManagerMock(),
$this->getConfigStorageMock('active'),
$this->getConfigStorageMock('extension'),
$this->getConfigStorageMock('optional'),
$this->getConfigFactoryMock(),
$this->getEventDispatcherMock());
}
/**
* @covers \Drupal\config_update\ConfigReverter::getFromActive
* @dataProvider getFromActiveProvider
*/
public function testGetFromActive($a, $b, $expected) {
$this->assertEquals($expected, $this->configReverter->getFromActive($a, $b));
}
/**
* Data provider for self:testGetFromActive().
*/
public function getFromActiveProvider() {
return [
// Arguments are $type, $name, and return value is the config.
// Some config items that are already prefixed.
['', 'foo.bar.one', ['foo.bar.one' => 'active', 'id' => 'one']],
['system.simple', 'foo.bar.one',
['foo.bar.one' => 'active', 'id' => 'one'],
],
// Config item with a defined entity definition prefix. Entity type 'foo'
// has prefix 'foo.bar'.
['foo', 'one', ['foo.bar.one' => 'active', 'id' => 'one']],
// Unknown type. This should not generate a call into the config read,
// so should not return the known value.
['unknown', 'foo.bar.one', FALSE],
// Missing configuration. Config mock is configured to return FALSE for
// this particular config name.
['system.simple', 'missing', FALSE],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::getFromExtension
* @dataProvider getFromExtensionProvider
*/
public function testGetFromExtension($a, $b, $expected) {
$this->assertEquals($expected, $this->configReverter->getFromExtension($a, $b));
}
/**
* Data provider for self:testGetFromExtension().
*/
public function getFromExtensionProvider() {
return [
// Arguments are $type, $name, and return value is the config.
// Some config items that are already prefixed, and exist in the mock
// extension storage.
['', 'in.extension', ['in.extension' => 'extension']],
['system.simple', 'in.extension', ['in.extension' => 'extension']],
// Config item with a defined entity definition prefix. Entity type 'foo'
// has prefix 'foo.bar'.
['foo', 'one', ['foo.bar.one' => 'extension', 'id' => 'one']],
// One that exists in both extension and optional storage.
['system.simple', 'in.both', ['in.both' => 'extension']],
// One that exists only in optional storage.
['system.simple', 'in.optional', ['in.optional' => 'optional']],
// Unknown type. This should not generate a call into the config read,
// so should not return the known value.
['unknown', 'in.extension', FALSE],
// Missing configuration. Storage mock is configured to return FALSE for
// this particular config name.
['system.simple', 'missing2', FALSE],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::import
* @dataProvider importProvider
*/
public function testImport($type, $name, $config_name, $expected, $config_before, $config_after) {
// Clear dispatch log and set pre-config.
$this->dispatchedEvents = [];
if ($config_name) {
$this->configStorage[$config_name] = $config_before;
}
$save_config = $this->configStorage;
// Call the importer and test the Boolean result.
$result = $this->configReverter->import($type, $name);
$this->assertEquals($result, $expected);
if ($result) {
// Verify that the config is correct after import, and logging worked.
$this->assertEquals($this->configStorage[$config_name], $config_after);
$this->assertEquals(count($this->dispatchedEvents), 1);
$this->assertEquals($this->dispatchedEvents[0][0], ConfigRevertInterface::IMPORT);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($this->configStorage, $save_config);
$this->assertEquals(count($this->dispatchedEvents), 0);
}
}
/**
* Data provider for self:testImport().
*/
public function importProvider() {
return [
// Elements: type, name, config name, return value,
// config to set up before, config expected after. See also
// getFromExtensionProvider().
[
'system.simple',
'in.extension',
'in.extension',
TRUE,
['in.extension' => 'before'],
['in.extension' => 'extension', '_core' => 'core_for_in.extension'],
],
[
'foo',
'one',
'foo.bar.one',
TRUE,
['foo.bar.one' => 'before', 'id' => 'one'],
[
'foo.bar.one' => 'extension',
'id' => 'one',
'_core' => 'core_for_foo.bar.one',
],
],
[
'system.simple',
'in.both',
'in.both',
TRUE,
['in.both' => 'before'],
['in.both' => 'extension', '_core' => 'core_for_in.both'],
],
[
'system.simple',
'in.optional',
'in.optional',
TRUE,
['in.optional' => 'before'],
['in.optional' => 'optional', '_core' => 'core_for_in.optional'],
],
[
'unknown',
'in.extension',
FALSE,
FALSE,
FALSE,
FALSE,
],
[
'system.simple',
'missing2',
'missing2',
FALSE,
FALSE,
FALSE,
],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::revert
* @dataProvider revertProvider
*/
public function testRevert($type, $name, $config_name, $expected, $config_before, $config_after) {
// Clear dispatch log and set pre-config.
$this->dispatchedEvents = [];
if ($config_name) {
$this->configStorage[$config_name] = $config_before;
}
$save_config = $this->configStorage;
// Call the reverter and test the Boolean result.
$result = $this->configReverter->revert($type, $name);
$this->assertEquals($result, $expected);
if ($result) {
// Verify that the config is correct after revert, and logging worked.
$this->assertEquals($this->configStorage[$config_name], $config_after);
$this->assertEquals(count($this->dispatchedEvents), 1);
$this->assertEquals($this->dispatchedEvents[0][0], ConfigRevertInterface::REVERT);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($this->configStorage, $save_config);
$this->assertEquals(count($this->dispatchedEvents), 0);
}
}
/**
* Data provider for self:testRevert().
*/
public function revertProvider() {
return [
// Elements: type, name, config name, return value,
// config to set up before, config expected after. See also
// getFromExtensionProvider().
[
'system.simple',
'in.extension',
'in.extension',
TRUE,
['in.extension' => 'active'],
['in.extension' => 'extension', '_core' => 'core_for_in.extension'],
],
[
'foo',
'one',
'foo.bar.one',
TRUE,
['foo.bar.one' => 'active', 'id' => 'one'],
[
'foo.bar.one' => 'extension',
'id' => 'one',
'_core' => 'core_for_foo.bar.one',
],
],
[
'system.simple',
'in.both',
'in.both',
TRUE,
['in.both' => 'active'],
['in.both' => 'extension', '_core' => 'core_for_in.both'],
],
[
'system.simple',
'in.optional',
'in.optional',
TRUE,
['in.optional' => 'active'],
['in.optional' => 'optional', '_core' => 'core_for_in.optional'],
],
[
'unknown',
'in.extension',
FALSE,
FALSE,
FALSE,
FALSE,
],
// Missing from extension storage.
[
'system.simple',
'missing2',
'missing2',
FALSE,
FALSE,
FALSE,
],
// Present in extension storage but missing from active storage.
[
'system.simple',
'another',
'another',
FALSE,
FALSE,
FALSE,
],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::delete
* @dataProvider deleteProvider
*/
public function testDelete($type, $name, $config_name, $expected) {
// Clear dispatch log.
$this->dispatchedEvents = [];
$save_config = $this->configStorage;
// Call the deleteer and test the Boolean result.
$result = $this->configReverter->delete($type, $name);
$this->assertEquals($result, $expected);
if ($result) {
// Verify that the config is missing after delete, and logging worked.
$this->assertTrue(!isset($this->configStorage[$config_name]));
$this->assertEquals(count($this->dispatchedEvents), 1);
$this->assertEquals($this->dispatchedEvents[0][0], ConfigDeleteInterface::DELETE);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($this->configStorage, $save_config);
$this->assertEquals(count($this->dispatchedEvents), 0);
}
}
/**
* Data provider for self:testDelete().
*/
public function deleteProvider() {
return [
// Elements: type, name, config name, return value.
[
'system.simple',
'in.extension',
'in.extension',
TRUE,
],
[
'foo',
'one',
'foo.bar.one',
TRUE,
],
[
'unknown',
'in.extension',
FALSE,
FALSE,
],
[
'system.simple',
'missing2',
'missing2',
FALSE,
],
];
}
}
@@ -0,0 +1,547 @@
<?php
namespace Drupal\Tests\config_update\Unit;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\EventDispatcher\Event;
/**
* Base class for unit testing in Config Update Manager.
*
* This class provides some mock classes for unit testing.
*/
abstract class ConfigUpdateUnitTestBase extends UnitTestCase {
/**
* The mocked entity definition information.
*
* They are not sorted, to test that the methods sort them. Also there are a
* couple with prefixes that are subsets of each other.
*
* @var string[]
*
* @see ConfigUpdateUnitTestBase::getEntityManagerMock().
*/
protected $entityDefinitionInformation = [
['prefix' => 'foo.bar', 'type' => 'foo'],
['prefix' => 'foo.barbaz', 'type' => 'bar'],
['prefix' => 'baz.foo', 'type' => 'baz'],
];
/**
* Creates a mock entity manager for the test.
*
* @see ConfigUpdateUnitTestBase::entityDefinitionInformation
*/
protected function getEntityManagerMock() {
$definitions = [];
$map = [];
foreach ($this->entityDefinitionInformation as $info) {
$def = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityTypeInterface')->getMock();
$def
->expects($this->any())
->method('getConfigPrefix')
->willReturn($info['prefix']);
$def
->expects($this->any())
->method('entityClassImplements')
->willReturn(TRUE);
$def
->method('getKey')
->willReturn('id');
$def->getConfigPrefix();
$definitions[$info['type']] = $def;
$map[] = [$info['type'], FALSE, $def];
$map[] = [$info['type'], TRUE, $def];
}
// Add in a content entity definition, which shouldn't be recognized by the
// config lister class.
$def = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityTypeInterface')->getMock();
$def
->expects($this->any())
->method('entityClassImplements')
->willReturn(FALSE);
$definitions['content_entity'] = $def;
$manager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')->getMock();
$manager
->method('getDefinitions')
->willReturn($definitions);
$manager
->method('getDefinition')
->will($this->returnValueMap($map));
$manager
->method('getStorage')
->will($this->returnCallback([$this, 'mockGetStorage']));
return $manager;
}
/**
* Mocks the getStorage() method for the entity manager.
*/
public function mockGetStorage($entity_type) {
// Figure out the config prefix for this entity type.
$prefix = '';
foreach ($this->entityDefinitionInformation as $info) {
if ($info['type'] == $entity_type) {
$prefix = $info['prefix'];
}
}
// This is used in ConfigReverter::import(). Although it is supposed to
// be entity storage, we'll use our mock config object instead.
return new MockConfig('', $prefix, $this);
}
/**
* Array of active configuration information for mocking.
*
* Array structure: Each element is an array whose first element is a
* provider name, and second is an array of config items it provides.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getConfigStorageMock()
*/
protected $configStorageActiveInfo = [
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.three']],
['foo.barbaz', ['foo.barbaz.four', 'foo.barbaz.five', 'foo.barbaz.six']],
['baz.foo', []],
['',
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
],
];
/**
* Array of extension configuration information for mocking.
*
* Array structure: Each element is an array whose first element is a
* provider name, and second is an array of config items it provides.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getConfigStorageMock()
*/
protected $configStorageExtensionInfo = [
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.seven']],
['baz.foo', []],
// This next item is assumed to be element 2 of the array. If not, you
// will need to change ConfigUpdateUnitTestBase::getConfigStorageMock().
['',
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.seven',
'foo.barbaz.four',
'foo.barnot.three',
'something.else',
],
],
];
/**
* Array of optional configuration information for mocking.
*
* Array structure: Each element is an array whose first element is a
* provider name, and second is an array of config items it provides.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getConfigStorageMock()
*/
protected $configStorageOptionalInfo = [
['foo.bar', []],
['foo.barbaz', ['foo.barbaz.four']],
// This next item is assumed to be element 2 of the array. If not, you
// will need to change ConfigUpdateUnitTestBase::getConfigStorageMock().
['', ['foo.barbaz.four']],
];
/**
* Creates a mock config storage object for the test.
*
* @param string $type
* Type of storage object to return: 'active', 'extension', or 'optional'.
* In active storage, the read() method is mocked to assume you are reading
* core.extension to get the profile name, so it returns that information.
* For extension and optional storage, the getComponentNames() method is
* mocked, and for all storages, the listAll() method is mocked.
*
* @see ConfigUpdateUnitTestBase::configStorageActiveInfo
* @see ConfigUpdateUnitTestBase::configStorageExtensionInfo
* @see ConfigUpdateUnitTestBase::configStorageOptionalInfo
*/
protected function getConfigStorageMock($type) {
if ($type == 'active') {
$storage = $this->getMockBuilder('Drupal\Core\Config\StorageInterface')->getMock();
// Various tests assume various values of configuration that need to be
// read from active storage.
$map = [
['core.extension', ['profile' => 'standard']],
['foo.bar.one', ['foo.bar.one' => 'active', 'id' => 'one']],
['missing', FALSE],
['in.extension',
['in.extension' => 'active', '_core' => 'core_for_in.extension'],
],
['in.both', ['in.both' => 'active']],
['in.optional', ['in.optional' => 'active']],
];
$storage
->method('read')
->will($this->returnValueMap($map));
$storage
->method('listAll')
->will($this->returnValueMap($this->configStorageActiveInfo));
}
elseif ($type == 'extension') {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$value = [];
foreach ($this->configStorageExtensionInfo[2][1] as $item) {
$value[$item] = 'ignored';
}
$storage
->method('getComponentNames')
->willReturn($value);
$storage
->method('listAll')
->will($this->returnValueMap($this->configStorageExtensionInfo));
$map = [
['in.extension', ['in.extension' => 'extension']],
['in.both', ['in.both' => 'extension']],
['in.optional', FALSE],
['foo.bar.one', ['foo.bar.one' => 'extension', 'id' => 'one']],
['another', ['another' => 'extension', 'id' => 'one']],
['missing2', FALSE],
];
$storage
->method('read')
->will($this->returnValueMap($map));
}
else {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$value = [];
foreach ($this->configStorageOptionalInfo[2][1] as $item) {
$value[$item] = 'ignored';
}
$storage
->method('getComponentNames')
->willReturn($value);
$storage
->method('listAll')
->will($this->returnValueMap($this->configStorageOptionalInfo));
$map = [
['in.optional', ['in.optional' => 'optional']],
['in.both', ['in.both' => 'optional']],
['missing2', FALSE],
];
$storage
->method('read')
->will($this->returnValueMap($map));
}
return $storage;
}
/**
* Creates a mock module handler for the test.
*/
protected function getModuleHandlerMock() {
$manager = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandlerInterface')->getMock();
$manager->method('getModuleList')
->willReturn(['foo_module' => '', 'standard' => '']);
return $manager;
}
/**
* Creates a mock theme handler for the test.
*/
protected function getThemeHandlerMock() {
$manager = $this->getMockBuilder('Drupal\Core\Extension\ThemeHandlerInterface')->getMock();
$manager->method('listInfo')
->willReturn(['foo_theme' => '']);
return $manager;
}
/**
* Creates a mock string translation class for the test.
*/
protected function getTranslationMock() {
$translation = $this->getMockBuilder('Drupal\Core\StringTranslation\TranslationInterface')->getMock();
$translation
->method('translateString')
->will($this->returnCallback([$this, 'mockTranslate']));
return $translation;
}
/**
* Mocks the translateString() method for the string translation mock object.
*
* @param \Drupal\Core\StringTranslation\TranslatableMarkup $input
* Object to translate.
*
* @return string
* The untranslated string from $input.
*/
public function mockTranslate(TranslatableMarkup $input) {
return $input->getUntranslatedString();
}
/**
* List of mock-dispatched events.
*
* Each element of the array is the call parameters to dispatchEvent() in
* the mocked dispatch class: name and event instance.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getEventDispatcherMock()
*/
protected $dispatchedEvents = [];
/**
* Mocks the event dispatcher service.
*
* Stores dispatched events in ConfigUpdateUnitTestBase::dispatchedEvents.
*/
protected function getEventDispatcherMock() {
$event = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
$event
->method('dispatch')
->will($this->returnCallback([$this, 'mockDispatch']));
return $event;
}
/**
* Mocks event dispatch.
*
* For \Symfony\Component\EventDispatcher\EventDispatchInterface::dispatch().
*/
public function mockDispatch($name, Event $event = NULL) {
$this->dispatchedEvents[] = [$name, $event];
}
/**
* Mock config storage for the mock config factory.
*
* This is actually managed by the MockConfig class in this file.
*
* @var array
*/
protected $configStorage = [];
/**
* Gets the value of the mocked config storage.
*/
public function getConfigStorage() {
return $this->configStorage;
}
/**
* Sets the value of the mocked config storage.
*/
public function setConfigStorage($values) {
$this->configStorage = $values;
}
/**
* Creates a mock config factory class for the test.
*/
protected function getConfigFactoryMock() {
$config = $this->getMockBuilder('Drupal\Core\Config\ConfigFactoryInterface')->getMock();
$config
->method('getEditable')
->will($this->returnCallback([$this, 'mockGetEditable']));
return $config;
}
/**
* Mocks the getEditable() method for the mock config factory.
*
* @param string $name
* Name of the config object to get an editable object for.
*
* @return MockConfig
* Editable mock config object.
*/
public function mockGetEditable($name) {
return new MockConfig($name, '', $this);
}
}
/**
* Mock class for mutable configuration, config entity, and entity storage.
*/
class MockConfig {
/**
* Name of the config.
*
* @var string
*/
protected $name = '';
/**
* Prefix for the entity type being mocked, for entity storage mocking.
*
* @var string
*/
protected $entityPrefix = '';
/**
* Test class this comes from.
*
* @var \Drupal\Tests\config_update\Unit\ConfigUpdateUnitTestBase
*/
protected $test;
/**
* Current value of the configuration.
*
* @var array
*/
protected $value = '';
/**
* Constructs a mock config object.
*
* @param string $name
* Name of the config that is being mocked. Can be blank.
* @param string $entity_prefix
* Prefix for the entity type that is being mocked. Often blank.
* @param \Drupal\Tests\config_update\Unit\ConfigUpdateUnitTestBase $test
* Test class this comes from.
*/
public function __construct($name, $entity_prefix, ConfigUpdateUnitTestBase $test) {
$this->name = $name;
$this->entityPrefix = $entity_prefix;
$this->test = $test;
$storage = $test->getConfigStorage();
if ($name && isset($storage[$name])) {
$value = $storage[$name];
$value['is_new'] = FALSE;
}
else {
$value['is_new'] = TRUE;
}
$value['_core'] = 'core_for_' . $name;
$this->value = $value;
}
/**
* Gets a component of the configuration value.
*/
public function get($key) {
return isset($this->value[$key]) ? $this->value[$key] : NULL;
}
/**
* Sets a component of the configuration value.
*/
public function set($key, $value) {
$this->value[$key] = $value;
return $this;
}
/**
* Sets the entire configuration value.
*/
public function setData($value) {
// Retain the _core key.
$core = isset($this->value['_core']) ? $this->value['_core'] : '';
$this->value = $value;
if ($core) {
$this->value['_core'] = $core;
}
return $this;
}
/**
* Saves the configuration.
*/
public function save() {
$config = $this->test->getConfigStorage();
$config[$this->name] = $this->value;
$this->test->setConfigStorage($config);
return $this;
}
/**
* Deletes the configuration.
*/
public function delete() {
$config = $this->test->getConfigStorage();
unset($config[$this->name]);
$this->test->setConfigStorage($config);
return $this;
}
/**
* Mocks the createFromStorageRecord() method from entity storage.
*/
public function createFromStorageRecord($values) {
if (!$this->entityPrefix) {
return NULL;
}
// This is supposed to return an entity, but the only method we need is
// save(), so instead set up and return this object.
$this->name = $this->entityPrefix . '.' . $values['id'];
$this->value = $values;
$this->value['_core'] = 'core_for_' . $this->name;
return $this;
}
/**
* Mocks the updateFromStorageRecord() method from entity storage.
*/
public function updateFromStorageRecord($object, $values) {
return $object->createFromStorageRecord($values);
}
/**
* Mocks the load() method for entity storage.
*/
public function load($id) {
$full_name = $this->entityPrefix . '.' . $id;
$configs = $this->test->getConfigStorage();
if (isset($configs[$full_name])) {
$this->value = $configs[$full_name];
$this->name = $full_name;
$this->value['_core'] = 'core_for_' . $full_name;
return $this;
}
return NULL;
}
}
@@ -4,21 +4,13 @@ sudo: false
matrix:
fast_finish: true
include:
- env: DRUPAL=8.5.x
php: 5.5
- env: DRUPAL=8.5.x
php: 5.6
- env: DRUPAL=8.5.x
- env: DRUPAL=8.6.x
php: 7.1
- env: DRUPAL=8.5.x
- env: DRUPAL=8.6.x
php: 7.2
- env: DRUPAL=8.6.x
php: 5.5
- env: DRUPAL=8.6.x
php: 5.6
- env: DRUPAL=8.6.x
- env: DRUPAL=8.7.x
php: 7.1
- env: DRUPAL=8.6.x
- env: DRUPAL=8.7.x
php: 7.2
addons:
@@ -94,11 +86,11 @@ before_script:
- until curl -s example.com:8080; do true; done > /dev/null
script:
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_access
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_alias
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain_alias
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_config
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_content
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_source
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_access
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_alias
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain_alias
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_config
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_content
- php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_source
@@ -16,6 +16,7 @@ Changelog
12-FEB-2018 8.x-1.0-alpha12
07-MAR-2018 8.x-1.0-alpha13
19-OCT-2018 8.x-1.0-alpha14
21-FEB-2019 8.x-1.0-alpha15
Status
====
@@ -113,7 +114,7 @@ marked with [x] are considered complete.
- [x] Check domain responses on configuration forms
- [x] Remove deprecated `entity_get_form_display`
- [x] Implement theme functions or twig templates where proper
- [ ] Advanced drush integration / complete labelled tasks
- [x] Advanced drush integration / complete labelled tasks
- [ ] Add filter options to domain_access and domain_source views
- [ ] Test cron handling
- [ ] Caching strategies in DomainNegotiator
@@ -199,23 +199,16 @@ a beta release on Drupal.org.
We would like to tackle issues in that order, but feel free to work on what motivates you.
Testing
Testing [![Build Status](https://travis-ci.com/agentrickard/domain.svg?branch=8.x-1.x)](https://travis-ci.com/agentrickard/domain)
====
@zerolab built a Travis definition file for automated testing! That means all pull requests will automatically run tests!
[![Build Status](https://travis-ci.org/agentrickard/domain.svg?branch=8.x-1.x)](https://travis-ci.org/agentrickard/domain)
The module does have solid test coverage, and complete coverage is required for release.
Right now, we mostly use SimpleTest, because it is most familiar, and much of our
testing is about browser and http behavior.
If you file a pull request or patch, please (at a minimum) run the existing tests to check
for failures. Writing additional tests will greatly speed completion, as I won't commit
code without test coverage.
New tests should be written as Functional, Kernel, or Unit tests. Conversion patches that
move Simpletests to Functional tests are welcome.
New tests should be written in PHPUnit as Functional, Kernel, or Unit tests.
Because Domain requires varying http host requests to test, we can't normally use the Drupal.org
testing infrastructure. (This may change, but we are not counting on it.)
@@ -227,5 +220,3 @@ point to your drupal instance. I use variants of `example.com` for local tests.
in most test cases. See `DomainTestBase::domainCreateTestDomains()` for the logic.
When running tests, you normally need to be on the default domain.
If anyone is capable of building a vagrant box to simplify testing, that would be ideal.
@@ -13,5 +13,14 @@
"issues": "http://drupal.org/project/issues/domain",
"irc": "http://irc.freenode.org/drupal-contribute",
"source": "http://cgit.drupalcode.org/domain"
},
"extra": {
"drush": {
"services": {
"domain/drush.services.yml": "^9",
"domain_access/drush.services.yml": "^9",
"domain_source/drush.services.yml": "^9"
}
}
}
}
@@ -9,8 +9,8 @@ dependencies:
- drupal:options
configure: domain.admin
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -159,3 +159,19 @@ function domain_theme() {
function template_preprocess_domain_nav_block(array &$variables) {
$variables['items'] = $variables['items']['#items'];
}
/**
* Implements hook_hook_info().
*/
function domain_hook_info() {
$hooks['domain_request_alter'] = [
'group' => 'domain',
];
$hooks['domain_validate_alter'] = [
'group' => 'domain',
];
$hooks['domain_references_alter'] = [
'group' => 'domain',
];
return $hooks;
}
@@ -0,0 +1,5 @@
services:
domain.commands:
class: \Drupal\domain\Commands\DomainCommands
tags:
- { name: drush.command }
@@ -75,17 +75,17 @@ class DomainAccessCheck implements AccessCheckInterface {
// Is the domain allowed?
// No domain, let it pass.
if (empty($domain)) {
return AccessResult::allowed()->setCacheMaxAge(0);
return AccessResult::allowed()->addCacheTags(['url.site']);
}
// Active domain, let it pass.
if ($domain->status()) {
return AccessResult::allowed()->setCacheMaxAge(0);
return AccessResult::allowed()->addCacheTags(['url.site']);
}
// Inactive domain, require permissions.
else {
$permissions = ['administer domains', 'access inactive domains'];
$operator = 'OR';
return AccessResult::allowedIfHasPermissions($account, $permissions, $operator)->setCacheMaxAge(0);
return AccessResult::allowedIfHasPermissions($account, $permissions, $operator)->addCacheTags(['url.site']);
}
}
@@ -0,0 +1,10 @@
<?php
/**
*
*/
namespace Drupal\domain\Commands;
class DomainCommandException extends \Exception {
}
File diff suppressed because it is too large Load Diff
@@ -59,8 +59,15 @@ class DomainElementManager implements DomainElementManagerInterface {
return $form;
}
$fields = $this->fieldList($field_name);
$empty = FALSE;
$disallowed = $this->disallowedOptions($form_state, $form[$field_name]);
$empty = empty($form[$field_name]['widget']['#options']);
if (empty($form[$field_name]['widget']['#options']) ||
(count($form[$field_name]['widget']['#options']) == 1 &&
isset($form[$field_name]['widget']['#options']['_none'])
)
) {
$empty = TRUE;
}
// If the domain form element is set as a group, and the field is not
// assigned to another group, then move it. See
@@ -68,7 +75,10 @@ class DomainElementManager implements DomainElementManagerInterface {
if (isset($form['domain']) && !isset($form[$field_name]['#group'])) {
$form[$field_name]['#group'] = 'domain';
}
// If no values and we should hide the element, do so.
if ($hide_on_disallow && $empty) {
$form[$field_name]['#access'] = FALSE;
}
// Check for domains the user cannot access or the absence of any options.
if (!empty($disallowed) || $empty) {
// @TODO: Potentially show this information to users with permission.
@@ -38,7 +38,7 @@ class DomainNegotiator implements DomainNegotiatorInterface {
/**
* The domain storage class.
*
* @var \Drupal\domain\DomainStorageInterface
* @var \Drupal\domain\DomainStorageInterface|null
*/
protected $domainStorage;
@@ -87,7 +87,6 @@ class DomainNegotiator implements DomainNegotiatorInterface {
$this->requestStack = $requestStack;
$this->moduleHandler = $module_handler;
$this->entityTypeManager = $entity_type_manager;
$this->domainStorage = $this->entityTypeManager->getStorage('domain');
$this->configFactory = $config_factory;
}
@@ -98,7 +97,7 @@ class DomainNegotiator implements DomainNegotiatorInterface {
// @TODO: Investigate caching methods.
$this->setHttpHost($httpHost);
// Try to load a direct match.
if ($domain = $this->domainStorage->loadByHostname($httpHost)) {
if ($domain = $this->domainStorage()->loadByHostname($httpHost)) {
// If the load worked, set an exact match flag for the hook.
$domain->setMatchType(self::DOMAIN_MATCH_EXACT);
}
@@ -107,16 +106,10 @@ class DomainNegotiator implements DomainNegotiatorInterface {
else {
$values = ['hostname' => $httpHost];
/** @var \Drupal\domain\DomainInterface $domain */
$domain = $this->domainStorage->create($values);
$domain = $this->domainStorage()->create($values);
$domain->setMatchType(self::DOMAIN_MATCH_NONE);
}
// Make sure all modules are loaded and can alter the found domains.
// See https://www.drupal.org/node/2896434#comment-12267208.
$this->moduleHandler->reload();
foreach ($this->moduleHandler->getImplementations('domain_request_alter') as $module) {
$this->moduleHandler->load($module);
}
// Now check with modules (like Domain Alias) that register alternate
// lookup systems with the main module.
$this->moduleHandler->alter('domain_request', $domain);
@@ -126,7 +119,7 @@ class DomainNegotiator implements DomainNegotiatorInterface {
$this->setActiveDomain($domain);
}
// Fallback to default domain if no match.
elseif ($domain = $this->domainStorage->loadDefaultDomain()) {
elseif ($domain = $this->domainStorage()->loadDefaultDomain()) {
$this->moduleHandler->alter('domain_request', $domain);
$domain->setMatchType(self::DOMAIN_MATCH_NONE);
if (!empty($domain->id())) {
@@ -180,7 +173,7 @@ class DomainNegotiator implements DomainNegotiatorInterface {
$httpHost = $_SERVER['HTTP_HOST'];
}
$hostname = !empty($httpHost) ? $httpHost : 'localhost';
return $this->domainStorage->prepareHostname($hostname);
return $this->domainStorage()->prepareHostname($hostname);
}
/**
@@ -202,13 +195,13 @@ class DomainNegotiator implements DomainNegotiatorInterface {
*/
public function isRegisteredDomain($hostname) {
// Direct hostname match always passes.
if ($domain = $this->domainStorage->loadByHostname($hostname)) {
if ($domain = $this->domainStorage()->loadByHostname($hostname)) {
return TRUE;
}
// Check for registered alias matches.
$values = ['hostname' => $hostname];
/** @var \Drupal\domain\DomainInterface $domain */
$domain = $this->domainStorage->create($values);
$domain = $this->domainStorage()->create($values);
$domain->setMatchType(self::DOMAIN_MATCH_NONE);
// Now check with modules (like Domain Alias) that register alternate
@@ -222,4 +215,17 @@ class DomainNegotiator implements DomainNegotiatorInterface {
return FALSE;
}
/**
* Retrieves the domain storage handler.
*
* @return \Drupal\domain\DomainStorageInterface
* The domain storage handler.
*/
protected function domainStorage() {
if (!$this->domainStorage) {
$this->domainStorage = $this->entityTypeManager->getStorage('domain');
}
return $this->domainStorage;
}
}
@@ -132,10 +132,9 @@ class DomainValidator implements DomainValidatorInterface {
}
// We cannot know which Guzzle Exception class will be returned; be generic.
catch (RequestException $e) {
watchdog_exception('domain', $e);
// File a general server failure.
$domain->setResponse(500);
return;
return $domain->getResponse();
}
// Expected result (i.e. no exception thrown.)
$domain->setResponse($request->getStatusCode());
@@ -343,8 +343,8 @@ class Domain extends ConfigEntityBase implements DomainInterface {
$default->is_default = FALSE;
$default->save();
}
// Ensures we have a proper domain_id.
if ($this->isNew()) {
// Ensures we have a proper domain_id but does not erase existing ones.
if ($this->isNew() && empty($this->getDomainId())) {
$this->createDomainId();
}
// Prevent duplicate hostname.
@@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* id = "domain",
* label = @Translation("Domain"),
* context = {
* "entity:domain" = @ContextDefinition("entity:domain", label = @Translation("Domain"), required = TRUE)
* "entity:domain" = @ContextDefinition("entity:domain", label = @Translation("Domain"), required = FALSE)
* }
* )
*/
@@ -73,7 +73,12 @@ class Domain extends ConditionPluginBase implements ContainerFactoryPluginInterf
],
],
];
return parent::buildConfigurationForm($form, $form_state);
$form = parent::buildConfigurationForm($form, $form_state);
if (isset($form['context_mapping']['entity:domain']['#title'])) {
$form['context_mapping']['entity:domain']['#title'] = $this->t('Select the Domain condition');
$form['context_mapping']['entity:domain']['#description'] = $this->t('This value must be set to "Active domain" for the context to work.');
}
return $form;
}
/**
@@ -9,8 +9,8 @@ hidden: TRUE
dependencies:
- domain
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -9,8 +9,8 @@ hidden: TRUE
dependencies:
- domain
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -8,8 +8,8 @@ dependencies:
- drupal:node
- domain:domain
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -0,0 +1,5 @@
services:
domain_access.commands:
class: \Drupal\domain_access\Commands\DomainAccessCommands
tags:
- { name: drush.command }
@@ -0,0 +1,79 @@
<?php
namespace Drupal\domain_access\Commands;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Drupal\domain\Commands\DomainCommands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* Drush commands for the domain access module.
*
* These commands mainly extend base Domain commands. See the documentation at
* https://github.com/consolidation/annotated-command for details.
*/
class DomainAccessCommands extends DomainCommands {
/**
* Registers additional information to domain:info.
*
* @hook init domain:info
*/
public function initDomainInfo(InputInterface $input, AnnotationData $annotationData) {
// To add a field label, append to the 'field-labels' item.
// @TODO: watch https://github.com/consolidation/annotated-command/pull/174
$annotationData['field-labels'] .= "\n" . 'domain_access_entities: Domain access entities';
}
/**
* Provides additional information to domain:info.
*
* @hook alter domain:info
*/
public function alterDomainInfo($result, CommandData $commandData) {
// Display which entities are enabled for domain by checking for the fields.
$result['domain_access_entities'] = $this->getFieldEntities(DOMAIN_ACCESS_FIELD);
return $result;
}
/**
* @hook option domain:delete
*/
public function deleteOptions(Command $command, AnnotationData $annotationData) {
$command->addOption(
'content-assign',
'',
InputOption::VALUE_OPTIONAL,
'Reassign content for Domain Access',
null
);
}
/**
* @hook on-event domain-delete
*/
public function domainAccessDomainDelete($target_domain, $options) {
// Run our own deletion routine here.
if (is_null($options['content-assign'])) {
$policy_content = 'prompt';
}
if (!empty($options['content-assign'])) {
if (in_array($options['content-assign'], $this->reassignment_policies, TRUE)) {
$policy_content = $options['content-assign'];
}
}
$delete_options = [
'entity_filter' => 'node',
'policy' => $policy_content,
'field' => DOMAIN_ACCESS_FIELD,
];
return $this->doReassign($target_domain, $delete_options);
}
}
@@ -11,8 +11,8 @@ dependencies:
- domain_access
- taxonomy
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -7,8 +7,8 @@ package: Domain
dependencies:
- domain:domain
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -7,8 +7,8 @@ package: Domain
dependencies:
- domain:domain
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -3,7 +3,7 @@ services:
class: Drupal\domain_config\DomainConfigOverrider
tags:
- { name: config.factory.override, priority: -150}
arguments: ['@config.storage']
arguments: ['@config.storage', '@module_handler']
domain_config.library.discovery.collector:
decorates: library.discovery.collector
@@ -6,6 +6,7 @@ use Drupal\domain\DomainInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* Domain-specific config overrides.
@@ -29,6 +30,13 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface {
*/
protected $storage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The domain context of the request.
*
@@ -64,9 +72,12 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface {
*
* @param \Drupal\Core\Config\StorageInterface $storage
* The configuration storage engine.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(StorageInterface $storage) {
public function __construct(StorageInterface $storage, ModuleHandlerInterface $module_handler) {
$this->storage = $storage;
$this->moduleHandler = $module_handler;
}
/**
@@ -77,11 +88,16 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface {
static $lookups;
// Key should be a known length, so hash.
$key = md5(implode(':', $names));
if (isset($lookups[$key])) {
return $lookups[$key];
}
// Set the context of the override request.
if (empty($this->contextSet)) {
$this->initiateContext();
}
// Prepare our overrides.
$overrides = [];
// loadOverrides() runs on config entities, which means that if we try
// to run this routine on our own data, then we end up in an infinite loop.
@@ -92,9 +108,6 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface {
$lookups[$key] = $overrides;
return $overrides;
}
if (empty($this->contextSet)) {
$this->initiateContext();
}
if (!empty($this->domain)) {
foreach ($names as $name) {
$config_name = $this->getDomainConfigName($name, $this->domain);
@@ -198,7 +211,11 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface {
// run.
if (empty($this->domain)) {
$this->domain = $this->domainNegotiator->getActiveDomain(TRUE);
// Ensure the module hook cache is set properly.
// See https://www.drupal.org/project/domain/issues/3025541
$this->moduleHandler->resetImplementations();
}
}
}
@@ -0,0 +1,24 @@
name: "Domain config hook test"
description: "Support module for domain config testing."
type: module
package: Testing
# version: VERSION
# core: 8.x
hidden: TRUE
dependencies: []
# This module represents several services that could be provided by multiple modules in the Drupal community.
# The following are not playing nice together:
# - A page cache policy service that uses the config factory.
# - A module that implements hook_module_implements.
# - A random hook that hook_module_implements wishes to control.
# - The domain_config module (and domain negotiator service, I believe).
# When this module is functioning correctly, when a user logs in, there will not be a state key set.
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1550763190
@@ -0,0 +1,23 @@
<?php
/**
* @file
* Hook implementations for this module.
*/
/**
* Implements hook_user_login().
*/
function domain_config_hook_test_user_login($account) {
\Drupal::state()->set('domain_config_test__user_login', TRUE);
}
/**
* Implements hook_module_implements_alter().
*/
function domain_config_hook_test_module_implements_alter(&$implementations, $hook) {
if ($hook == 'user_login') {
// Turn off the domain_config_hook_test's hook_user_login (above).
unset($implementations['domain_config_hook_test']);
}
}
@@ -0,0 +1,7 @@
services:
domain_config_service.page_cache_request_policy:
class: Drupal\domain_config_hook_test\PageCache\RequestPolicy\PageCacheRequestPolicy
arguments: ['@config.factory']
tags:
- { name: page_cache_request_policy }
@@ -0,0 +1,47 @@
<?php
namespace Drupal\domain_config_hook_test\PageCache\RequestPolicy;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\Core\Session\SessionConfigurationInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* A page cache request policy.
*
* This service is not meant to DO anything, it's just meant to represent
* a service that might be present in the Drupal community. For example,
* persistent_login module has this same structure.
*/
class PageCacheRequestPolicy implements RequestPolicyInterface {
/**
* Drupal config factory.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* Constructor.
*
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* Drupal config factory.
*/
public function __construct(ConfigFactory $config_factory) {
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function check(Request $request) {
// This line is important. You have to use this service for it to fail.
$this->configFactory
->get('system.site');
return NULL;
}
}
@@ -10,8 +10,8 @@ dependencies:
- domain
- domain_config
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -10,8 +10,8 @@ dependencies:
- domain
- domain_config
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -0,0 +1,30 @@
<?php
namespace Drupal\Tests\domain_config\Functional;
/**
* Tests the domain config system.
*
* @group domain_config
*/
class DomainConfigHookProblemTest extends DomainConfigHookTest {
/**
* {@inheritdoc}
*/
public static $modules = [
// Adds the domain_config module.
'domain_config',
];
/**
* Disabled config schema checking.
*
* Domain Config actually duplicates schemas provided by other modules,
* so it cannot define its own.
*
* @var bool
*/
protected $strictConfigSchema = FALSE;
}
@@ -0,0 +1,42 @@
<?php
namespace Drupal\Tests\domain_config\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the domain config system.
*
* @group domain_config
*/
class DomainConfigHookTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'domain',
// See module info file for description of what this module does.
'domain_config_hook_test',
];
/**
* Test to ensure a domain_config_hook_test_user_login() does not run.
*
* This test serves as a control to show the domain_config_hook_test module
* functions correctly on it's own. Only when you add the domain_config
* module, does it fail.
*/
public function testHookRuns() {
$this->drupalGet('user/login');
$user = $this->drupalCreateUser([]);
$edit = ['name' => $user->getUserName(), 'pass' => $user->passRaw];
$this->drupalPostForm(NULL, $edit, t('Log in'));
$test = \Drupal::state()->get('domain_config_test__user_login', NULL);
// When this test passes, it means domain_config_hook_test_user_login was
// not run.
$this->assertNull($test, 'The hook_user_login state message is set.');
}
}
@@ -11,8 +11,8 @@ dependencies:
- domain:domain
- domain:domain_access
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -13,7 +13,7 @@ use Drupal\Core\Url;
class DomainContentController extends ControllerBase {
/**
* Builds the lost of domains and relevant entities.
* Builds the list of domains and relevant entities.
*
* @param array $options
* A list of variables required to build editor or content pages.
@@ -8,8 +8,8 @@ dependencies:
- drupal:node
- domain:domain
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -286,3 +286,17 @@ function domain_source_presave_generate(EntityInterface $entity) {
}
}
}
/**
* Implements hook_hook_info().
*/
function domain_source_hook_info() {
$hooks['domain_source_alter'] = [
'group' => 'domain_source',
];
$hooks['domain_source_path_alter'] = [
'group' => 'domain_source',
];
return $hooks;
}
@@ -0,0 +1,5 @@
services:
domain_source.commands:
class: \Drupal\domain_source\Commands\DomainSourceCommands
tags:
- { name: drush.command }
@@ -0,0 +1,79 @@
<?php
namespace Drupal\domain_source\Commands;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Drupal\domain\Commands\DomainCommands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* Drush commands for the domain source module.
*
* These commands mainly extend base Domain commands. See the documentation at
* https://github.com/consolidation/annotated-command for details.
*/
class DomainSourceCommands extends DomainCommands {
/**
* Registers additional information to domain:info.
*
* @hook init domain:info
*/
public function initDomainInfo(InputInterface $input, AnnotationData $annotationData) {
// To add a field label, append to the 'field-labels' item.
// @TODO: watch https://github.com/consolidation/annotated-command/pull/174
$annotationData['field-labels'] .= "\n" . 'domain_source_entities: Domain source entities';
}
/**
* Provides additional information to domain:info.
*
* @hook alter domain:info
*/
public function alterDomainInfo($result, CommandData $commandData) {
// Display which entities are enabled for domain by checking for the fields.
$result['domain_source_entities'] = $this->getFieldEntities(DOMAIN_SOURCE_FIELD);
return $result;
}
/**
* @hook option domain:delete
*/
public function deleteOptions(Command $command, AnnotationData $annotationData) {
$command->addOption(
'source-assign',
'',
InputOption::VALUE_OPTIONAL,
'Reassign content for Domain Source',
null
);
}
/**
* @hook on-event domain-delete
*/
public function domainSourceDomainDelete($target_domain, $options) {
// Run our own deletion routine here.
if (is_null($options['content-assign'])) {
$policy_content = 'prompt';
}
if (!empty($options['content-assign'])) {
if (in_array($options['content-assign'], $this->reassignment_policies, TRUE)) {
$policy_content = $options['content-assign'];
}
}
$delete_options = [
'entity_filter' => 'node',
'policy' => $policy_content,
'field' => DOMAIN_SOURCE_FIELD,
];
return $this->doReassign($target_domain, $delete_options);
}
}
@@ -76,7 +76,7 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface {
/**
* The domain storage.
*
* @var \Drupal\domain\DomainStorageInterface
* @var \Drupal\domain\DomainStorageInterface|null
*/
protected $domainStorage;
@@ -100,7 +100,6 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface {
$this->entityTypeManager = $entity_type_manager;
$this->aliasManager = $alias_manager;
$this->configFactory = $config_factory;
$this->domainStorage = $entity_type_manager->getStorage('domain');
}
/**
@@ -159,7 +158,7 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface {
$target_id = domain_source_get($entity);
}
if (!empty($target_id)) {
$source = $this->domainStorage->load($target_id);
$source = $this->domainStorage()->load($target_id);
}
$options['entity'] = $entity;
$options['entity_type'] = $entity->getEntityTypeId();
@@ -169,7 +168,7 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface {
else {
if (isset($options['domain_target_id'])) {
$target_id = $options['domain_target_id'];
$source = $this->domainStorage->load($target_id);
$source = $this->domainStorage()->load($target_id);
}
$this->moduleHandler->alter('domain_source_path', $source, $path, $options);
}
@@ -279,4 +278,17 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface {
return $this->activeDomain;
}
/**
* Retrieves the domain storage handler.
*
* @return \Drupal\domain\DomainStorageInterface
* The domain storage handler.
*/
protected function domainStorage() {
if (!$this->domainStorage) {
$this->domainStorage = $this->entityTypeManager->getStorage('domain');
}
return $this->domainStorage;
}
}
@@ -14,8 +14,8 @@ dependencies:
- serialization
- user
# Information added by Drupal.org packaging script on 2018-11-14
version: '8.x-1.0-alpha14+9-dev'
# Information added by Drupal.org packaging script on 2019-02-21
version: '8.x-1.0-alpha15'
core: '8.x'
project: 'domain'
datestamp: 1542230887
datestamp: 1550763190
@@ -141,4 +141,44 @@ class DomainSourceElementTest extends DomainTestBase {
$this->assert(strpos($url, 'node/' . $nid . '/edit') === FALSE, 'Form submitted.');
}
/**
* Test for https://www.drupal.org/project/domain/issues/3010256.
*/
public function testAnonForm() {
// Editor with no domain permissions should not see the element.
$editor = $this->drupalCreateUser([
'create article content',
]);
$this->drupalLogin($editor);
$this->drupalGet('node/add/article');
$this->assertSession()->statusCodeEquals(200);
$locator = DOMAIN_SOURCE_FIELD;
$this->assertSession()->fieldNotExists($locator);
// Editor with domain permissions should see the element once they
// are assigned to domains.
$editor2 = $this->drupalCreateUser([
'create article content',
'publish to any assigned domain',
]);
$this->drupalLogin($editor2);
$this->drupalGet('node/add/article');
$this->assertSession()->statusCodeEquals(200);
$locator = DOMAIN_SOURCE_FIELD;
$this->assertSession()->fieldNotExists($locator);
// Domain assignment.
$ids = ['example_com', 'one_example_com'];
$this->addDomainsToEntity('user', $editor2->id(), $ids, DOMAIN_ACCESS_FIELD);
$this->drupalGet('node/add/article');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldExists($locator);
}
}
@@ -1,58 +0,0 @@
diff --git a/domain_site_settings.services.yml b/domain_site_settings.services.yml
index 65e9113..6ebd87d 100644
--- a/domain_site_settings.services.yml
+++ b/domain_site_settings.services.yml
@@ -1,6 +1,6 @@
services:
domain_site_settings.overrider:
class: \Drupal\domain_site_settings\Configuration\DomainConfigOverride
- arguments: ['@domain.negotiator', '@config.factory']
+ arguments: ['@config.factory']
tags:
- {name: config.factory.override, priority: 5}
diff --git a/src/Configuration/DomainConfigOverride.php b/src/Configuration/DomainConfigOverride.php
index b2571b4..506feef 100644
--- a/src/Configuration/DomainConfigOverride.php
+++ b/src/Configuration/DomainConfigOverride.php
@@ -16,13 +16,6 @@ use Drupal\Core\Config\ConfigFactoryInterface;
*/
class DomainConfigOverride implements ConfigFactoryOverrideInterface {
- /**
- * The Domain negotiator.
- *
- * @var \Drupal\domain\DomainNegotiatorInterface
- */
- protected $negotiator;
-
/**
* The config factory.
*
@@ -33,16 +26,11 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface {
/**
* Constructs a DomainSourcePathProcessor object.
*
- * The domain loader.
- * @param \Drupal\domain\DomainNegotiatorInterface $negotiator
- * The domain negotiator.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The module handler service.
*/
public function __construct(
- DomainNegotiatorInterface $negotiator,
ConfigFactoryInterface $config_factory) {
- $this->negotiator = $negotiator;
$this->configFactory = $config_factory;
}
@@ -59,7 +47,9 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface {
public function loadOverrides($names) {
$overrides = array();
if (in_array('system.site', $names)) {
- $domain = $this->negotiator->getActiveDomain();
+ /* @var \Drupal\domain\DomainNegotiator $negotiator */
+ $negotiator = \Drupal::service('domain.negotiator');
+ $domain = $negotiator->getActiveDomain();
if (!empty($domain)) {
$domain_key = $domain->id();
$configFactory = $this->configFactory->get('domain_site_settings.domainconfigsettings');
@@ -2,15 +2,15 @@ name: Domain Site Settings
type: module
description: 'Basic Site Setting for Domains.'
dependencies:
- domain
- domain_config
- domain:domain
- domain:domain_config
package: Domain
# core: 8.x
configure: domain_site_settings.list
# Information added by Drupal.org packaging script on 2017-04-04
version: '8.x-1.2'
# Information added by Drupal.org packaging script on 2018-09-23
version: '8.x-1.3'
core: '8.x'
project: 'domain_site_settings'
datestamp: 1491339784
datestamp: 1537684984
@@ -4,4 +4,3 @@ domain_site_settings.menu_domain_list:
description: 'Change domains site name, email address, slogan, default front page, and error pages.'
parent: domain.admin
weight: 10
@@ -1,6 +1,6 @@
services:
domain_site_settings.overrider:
class: \Drupal\domain_site_settings\Configuration\DomainConfigOverride
arguments: ['@config.factory']
arguments: ['@domain.negotiator', '@config.factory']
tags:
- {name: config.factory.override, priority: 5}
@@ -5,7 +5,6 @@ namespace Drupal\domain_site_settings\Configuration;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\domain\DomainLoaderInterface;
use Drupal\domain\DomainNegotiatorInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
@@ -16,6 +15,13 @@ use Drupal\Core\Config\ConfigFactoryInterface;
*/
class DomainConfigOverride implements ConfigFactoryOverrideInterface {
/**
* The Domain negotiator.
*
* @var \Drupal\domain\DomainNegotiatorInterface
*/
protected $negotiator;
/**
* The config factory.
*
@@ -26,11 +32,15 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface {
/**
* Constructs a DomainSourcePathProcessor object.
*
* @param \Drupal\domain\DomainNegotiatorInterface $negotiator
* The domain negotiator.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The module handler service.
*/
public function __construct(
DomainNegotiatorInterface $negotiator,
ConfigFactoryInterface $config_factory) {
$this->negotiator = $negotiator;
$this->configFactory = $config_factory;
}
@@ -44,12 +54,10 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface {
* An array keyed by configuration name of override data. Override data
* contains a nested array structure of overrides.
*/
public function loadOverrides($names) {
$overrides = array();
public function loadOverrides($names = []) {
$overrides = [];
if (in_array('system.site', $names)) {
/* @var \Drupal\domain\DomainNegotiator $negotiator */
$negotiator = \Drupal::service('domain.negotiator');
$domain = $negotiator->getActiveDomain();
$domain = $this->negotiator->getActiveDomain();
if (!empty($domain)) {
$domain_key = $domain->id();
$configFactory = $this->configFactory->get('domain_site_settings.domainconfigsettings');
@@ -60,6 +68,8 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface {
$site_403 = $configFactory->get($domain_key . '.site_403');
$site_404 = $configFactory->get($domain_key . '.site_404');
$site_front = $configFactory->get($domain_key . '.site_frontpage');
$front = ($site_front !== \NULL) ? $site_front : '/node';
// Create the new settings array to override the configuration.
$overrides['system.site'] = [
'name' => $site_name,
@@ -68,7 +78,8 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface {
'page' => [
'403' => $site_403,
'404' => $site_404,
'front' => ($site_front !== NULL) ? $site_front : '/node']
'front' => $front,
],
];
}
}
@@ -25,7 +25,7 @@ class DomainSiteSettingsController extends ControllerBase {
/**
* Construct function.
*
* @param DomainLoader $domain_loader
* @param \Drupal\domain\DomainLoader $domain_loader
* Load the domain records.
*/
public function __construct(
@@ -37,7 +37,7 @@ class DomainSiteSettingsController extends ControllerBase {
/**
* Create function return static domain loader configuration.
*
* @param ContainerInterface $container
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* Load the ContainerInterface.
*
* @return \static
@@ -62,22 +62,22 @@ class DomainSiteSettingsController extends ControllerBase {
foreach ($domains as $domain) {
$row = [
$domain->label(),
$domain->id(),
Link::fromTextAndUrl($this->t('Edit'), Url::fromRoute('domain_site_settings.config_form', array('domain_id' => $domain->id()))),
$domain->getCanonical(),
Link::fromTextAndUrl($this->t('Edit'), Url::fromRoute('domain_site_settings.config_form', ['domain_id' => $domain->id()])),
];
$rows[] = $row;
}
// Build a render array which will be themed as a table.
$build['pager_example'] = array(
$build['pager_example'] = [
'#rows' => $rows,
'#header' => array(
'#header' => [
$this->t('Name'),
$this->t('Hostname'),
$this->t('Edit Settings')
),
$this->t('Edit Settings'),
],
'#type' => 'table',
'#empty' => $this->t('No domain record found.'),
);
];
return $build;
}
@@ -98,67 +98,64 @@ class DomainConfigSettingsForm extends ConfigFormBase {
$site_mail = $config->get($domain_id . '.site_mail');
}
$form['site_information'] = array(
$form['site_information'] = [
'#type' => 'details',
'#title' => $this->t('Site details'),
'#open' => TRUE,
);
$form['site_information']['site_name'] = array(
];
$form['site_information']['site_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Site name'),
'#default_value' => ($config->get($domain_id) != NULL) ? $config->get($domain_id . '.site_name') : $site_config->get('name'),
'#required' => TRUE,
);
$form['site_information']['site_slogan'] = array(
];
$form['site_information']['site_slogan'] = [
'#type' => 'textfield',
'#title' => $this->t('Slogan'),
'#default_value' => ($config->get($domain_id) != NULL) ? $config->get($domain_id . '.site_slogan') : $site_config->get('slogan'),
'#description' => $this->t("How this is used depends on your site's theme."),
);
$form['site_information']['site_mail'] = array(
];
$form['site_information']['site_mail'] = [
'#type' => 'email',
'#title' => $this->t('Email address'),
'#default_value' => $site_mail,
'#description' => $this->t("The <em>From</em> address in automated emails sent during registration and new password requests, and other notifications. (Use an address ending in your site's domain to help prevent this email being flagged as spam.)"),
'#required' => TRUE,
);
$form['front_page'] = array(
];
$form['front_page'] = [
'#type' => 'details',
'#title' => $this->t('Front page'),
'#open' => TRUE,
);
];
$front_page = $site_config->get('page.front') != '/user/login' ? $this->aliasManager->getAliasByPath($site_config->get('page.front')) : '';
$front_page = ($config->get($domain_id) != NULL) ? $config->get($domain_id . '.site_frontpage') : $front_page;
$form['front_page']['site_frontpage'] = array(
$form['front_page']['site_frontpage'] = [
'#type' => 'textfield',
'#title' => $this->t('Default front page'),
'#default_value' => $front_page,
'#size' => 40,
'#description' => $this->t('Optionally, specify a relative URL to display as the front page. Leave blank to display the default front page.'),
'#field_prefix' => $this->requestContext->getCompleteBaseUrl(),
);
$form['error_page'] = array(
];
$form['error_page'] = [
'#type' => 'details',
'#title' => $this->t('Error pages'),
'#open' => TRUE,
);
$form['error_page']['site_403'] = array(
];
$form['error_page']['site_403'] = [
'#type' => 'textfield',
'#title' => $this->t('Default 403 (access denied) page'),
'#default_value' => ($config->get($domain_id) !== NULL) ? $config->get($domain_id . '.site_403') : $site_config->get('page.403'),
'#size' => 40,
'#description' => $this->t('This page is displayed when the requested document is denied to the current user. Leave blank to display a generic "access denied" page.'),
);
$form['error_page']['site_404'] = array(
];
$form['error_page']['site_404'] = [
'#type' => 'textfield',
'#title' => $this->t('Default 404 (not found) page'),
'#default_value' => ($config->get($domain_id) !== NULL) ? $config->get($domain_id . '.site_404') : $site_config->get('page.404'),
'#size' => 40,
'#description' => $this->t('This page is displayed when no other content matches the requested document. Leave blank to display a generic "page not found" page.'),
);
];
$form['domain_id'] = [
'#type' => 'hidden',
'#title' => $this->t('Domain ID'),
@@ -185,7 +182,7 @@ class DomainConfigSettingsForm extends ConfigFormBase {
$form_state->setErrorByName('site_frontpage', $this->t("The path '%path' has to start with a slash.", ['%path' => $form_state->getValue('site_frontpage')]));
}
if (!$this->pathValidator->isValid($form_state->getValue('site_frontpage'))) {
$form_state->setErrorByName('site_frontpage', $this->t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state->getValue('site_frontpage'))));
$form_state->setErrorByName('site_frontpage', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_frontpage')]));
}
// Get the normal paths of both error pages.
if (!$form_state->isValueEmpty('site_403')) {
@@ -202,11 +199,11 @@ class DomainConfigSettingsForm extends ConfigFormBase {
}
// Validate 403 error path.
if (!$form_state->isValueEmpty('site_403') && !$this->pathValidator->isValid($form_state->getValue('site_403'))) {
$form_state->setErrorByName('site_403', $this->t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state->getValue('site_403'))));
$form_state->setErrorByName('site_403', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_403')]));
}
// Validate 404 error path.
if (!$form_state->isValueEmpty('site_404') && !$this->pathValidator->isValid($form_state->getValue('site_404'))) {
$form_state->setErrorByName('site_404', $this->t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state->getValue('site_404'))));
$form_state->setErrorByName('site_404', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_404')]));
}
parent::validateForm($form, $form_state);
@@ -1,6 +1,6 @@
Module: Matomo Analytics
Author: Alexander Hass <http://drupal.org/user/85918>
Author: Alexander Hass <https://drupal.org/user/85918>
Description
@@ -46,7 +46,7 @@ user with 'Administer Matomo' permission.
Like the blocks visibility settings in Drupal core, there is a choice for
"Add if the following PHP code returns TRUE." Sample PHP snippets that can be
used in this textarea can be found on the handbook page "Overview-approach to
block visibility" at http://drupal.org/node/64135.
block visibility" at https://drupal.org/node/64135.
Custom variables
=================
@@ -65,7 +65,7 @@ Value: [current-user:matomo-role-ids]
Scope: Visitor
More details about custom variables can be found in the Matomo API documentation
at http://matomo.org/docs/javascript-tracking/#toc-custom-variables.
at https://matomo.org/docs/javascript-tracking/#toc-custom-variables.
Advanced Settings
@@ -80,7 +80,7 @@ file locally.
Known issues
============
Drupal requirements (http://drupal.org/requirements) tell you to configure
Drupal requirements (https://drupal.org/requirements) tell you to configure
PHP with "session.save_handler = user", but your Matomo installation may
not work with this configuration and gives you a server error 500.
@@ -15,8 +15,10 @@
],
"support": {
"issues": "https://www.drupal.org/project/issues/matomo",
"source": "http://git.drupal.org/project/matomo.git"
"source": "https://git.drupal.org/project/matomo.git"
},
"license": "GPL-2.0+",
"require": {}
"require": {
"drupal/core": "~8.5"
}
}
@@ -4,12 +4,14 @@ description: "Adds Matomo javascript tracking code to all your site's pages."
package: Statistics
# core: 8.x
configure: matomo.admin_settings_form
dependencies:
- drupal:system (>= 8.5)
test_dependencies:
- php:php
- token:token
# Information added by Drupal.org packaging script on 2018-07-13
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2019-02-08
version: '8.x-1.9'
core: '8.x'
project: 'matomo'
datestamp: 1531470224
datestamp: 1549615102
@@ -17,7 +17,8 @@ function matomo_install() {
$role->grantPermission('opt-in or out of matomo tracking');
$success = $role->save();
if ($success) {
drupal_set_message(t('Module %module granted %permission permission to authenticated users.', ['%module' => 'Matomo Analytics', '%permission' => t('Opt-in or out of tracking')]), 'status');
$messenger = \Drupal::messenger();
$messenger->addMessage(t('Module %module granted %permission permission to authenticated users.', ['%module' => 'Matomo Analytics', '%permission' => t('Opt-in or out of tracking')]), 'status');
}
}
@@ -7,7 +7,7 @@
* Adds the required Javascript to all your Drupal pages to allow tracking by
* the Matomo statistics package.
*
* @author: Alexander Hass <http://drupal.org/user/85918>
* @author: Alexander Hass <https://drupal.org/user/85918>
*/
use Drupal\Component\Serialization\Json;
@@ -19,22 +19,18 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\matomo\MatomoInterface;
use Drupal\node\NodeInterface;
use GuzzleHttp\Exception\RequestException;
use Drupal\matomo\Component\Render\MatomoJavaScriptSnippet;
/**
* Define the default file extension list that should be tracked as download.
*/
define('MATOMO_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip');
/**
* Implements hook_help().
*/
function matomo_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'matomo.admin_settings_form':
return t('<a href=":pk_url">Matomo Analytics</a> is an open source (GPL license) web analytics software. It gives interesting reports on your website visitors, your popular pages, the search engines keywords they used, the language they speak... and so much more. Matomo aims to be an open source alternative to Google Analytics.', [':pk_url' => 'http://www.matomo.org/']);
return t('<a href=":pk_url">Matomo Analytics</a> is an open source (GPL license) web analytics software. It gives interesting reports on your website visitors, your popular pages, the search engines keywords they used, the language they speak... and so much more. Matomo aims to be an open source alternative to Google Analytics.', [':pk_url' => 'https://www.matomo.org/']);
}
}
@@ -114,7 +110,7 @@ function matomo_page_attachments(array &$page) {
'error' => t('Error message'),
];
foreach (drupal_get_messages(NULL, FALSE) as $type => $messages) {
foreach (\Drupal::messenger()->all(NULL, FALSE) as $type => $messages) {
// Track only the selected message types.
if (in_array($type, $message_types)) {
foreach ($messages as $message) {
@@ -198,7 +194,7 @@ function matomo_page_attachments(array &$page) {
$codesnippet_after = $config->get('codesnippet.after');
// Build tracker code.
// @see http://matomo.org/docs/javascript-tracking/#toc-asynchronous-tracking
// @see https://matomo.org/docs/javascript-tracking/#toc-asynchronous-tracking
$script = 'var _paq = _paq || [];';
$script .= '(function(){';
$script .= 'var u=(("https:" == document.location.protocol) ? "' . UrlHelper::filterBadProtocol($url_https) . '" : "' . UrlHelper::filterBadProtocol($url_http) . '");';
@@ -220,7 +216,7 @@ function matomo_page_attachments(array &$page) {
}
// Custom file download extensions.
if ($config->get('track.files') && !($config->get('track.files_extensions') == MATOMO_TRACKFILES_EXTENSIONS)) {
if ($config->get('track.files') && !($config->get('track.files_extensions') == MatomoInterface::MATOMO_TRACKFILES_EXTENSIONS)) {
$script .= '_paq.push(["setDownloadExtensions", ' . Json::encode($config->get('track.files_extensions')) . ']);';
}
@@ -279,7 +275,7 @@ function matomo_page_attachments(array &$page) {
];
// Disable the download & outlink tracking for specific CSS classes.
// Custom code snippets with 'setIgnoreClasses' will override the value.
// @see http://developer.matomo.org/api-reference/tracking-javascript#disable-the-download-amp-outlink-tracking-for-specific-css-classes
// @see https://developer.matomo.org/api-reference/tracking-javascript#disable-the-download-amp-outlink-tracking-for-specific-css-classes
$script .= '_paq.push(["setIgnoreClasses", ' . Json::encode($ignore_classes) . ']);';
// Enable download & outlink link tracking.
@@ -441,11 +437,12 @@ function matomo_user_profile_form_submit($form, FormStateInterface $form_state)
*/
function matomo_cron() {
$config = \Drupal::config('matomo.settings');
$request_time = \Drupal::time()->getRequestTime();
// Regenerate the piwik.js every day.
if (REQUEST_TIME - \Drupal::state()->get('matomo.last_cache') >= 86400 && $config->get('cache')) {
if ($request_time - \Drupal::state()->get('matomo.last_cache') >= 86400 && $config->get('cache')) {
_matomo_cache($config->get('url_http') . 'piwik.js', TRUE);
\Drupal::state()->set('matomo.last_cache', REQUEST_TIME);
\Drupal::state()->set('matomo.last_cache', $request_time);
}
}
@@ -4,15 +4,37 @@ namespace Drupal\matomo\Form;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\matomo\MatomoInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure Matomo settings for this site.
*/
class MatomoAdminSettingsForm extends ConfigFormBase {
protected $moduleHandler;
protected $currentUser;
protected $httpClient;
/**
* {@inheritdoc}
*/
public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $currentUser, ModuleHandler $moduleHandler, Client $httpClient) {
parent::__construct($config_factory);
$this->currentUser = $currentUser;
$this->moduleHandler = $moduleHandler;
$this->httpClient = $httpClient;
}
/**
* {@inheritdoc}
*/
@@ -117,7 +139,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
];
// Page specific visibility configurations.
$account = \Drupal::currentUser();
$account = $this->currentUser;
$php_access = $account->hasPermission('use php for matomo tracking visibility');
$visibility_request_path_pages = $config->get('visibility.request_path_pages');
@@ -140,7 +162,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
];
$description = $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", ['%blog' => '/blog', '%blog-wildcard' => '/blog/*', '%front' => '<front>']);
if (\Drupal::moduleHandler()->moduleExists('php') && $php_access) {
if ($this->moduleHandler->moduleExists('php') && $php_access) {
$options[] = $this->t('Pages on which this PHP code returns <code>TRUE</code> (experts only)');
$title = $this->t('Pages or PHP code');
$description .= ' ' . $this->t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', ['%php' => '<?php ?>']);
@@ -207,13 +229,13 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
1 => $this->t('Tracking on by default, users with %permission permission can opt out', $t_permission),
2 => $this->t('Tracking off by default, users with %permission permission can opt in', $t_permission),
],
'#default_value' => !empty($visibility_user_account_mode) ? $visibility_user_account_mode : 1,
'#default_value' => !empty($visibility_user_account_mode) ? $visibility_user_account_mode : 0,
];
$form['tracking']['user_visibility_settings']['matomo_trackuserid'] = [
'#type' => 'checkbox',
'#title' => $this->t('Track User ID'),
'#default_value' => $config->get('track.userid'),
'#description' => $this->t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. <a href=":url">Learn more about the benefits of using User ID</a>.', [':url' => 'http://matomo.org/docs/user-id/']),
'#description' => $this->t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. <a href=":url">Learn more about the benefits of using User ID</a>.', [':url' => 'https://matomo.org/docs/user-id/']),
];
// Link specific configurations.
@@ -237,7 +259,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
'#title_display' => 'invisible',
'#type' => 'textfield',
'#default_value' => $config->get('track.files_extensions'),
'#description' => $this->t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: @extensions', ['@extensions' => MATOMO_TRACKFILES_EXTENSIONS]),
'#description' => $this->t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: @extensions', ['@extensions' => MatomoInterface::MATOMO_TRACKFILES_EXTENSIONS]),
'#maxlength' => 500,
'#states' => [
'enabled' => [
@@ -251,7 +273,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
];
$colorbox_dependencies = '<div class="admin-requirements">';
$colorbox_dependencies .= $this->t('Requires: @module-list', ['@module-list' => (\Drupal::moduleHandler()->moduleExists('colorbox') ? $this->t('@module (<span class="admin-enabled">enabled</span>)', ['@module' => 'Colorbox']) : $this->t('@module (<span class="admin-missing">disabled</span>)', ['@module' => 'Colorbox']))]);
$colorbox_dependencies .= $this->t('Requires: @module-list', ['@module-list' => ($this->moduleHandler->moduleExists('colorbox') ? $this->t('@module (<span class="admin-enabled">enabled</span>)', ['@module' => 'Colorbox']) : $this->t('@module (<span class="admin-missing">disabled</span>)', ['@module' => 'Colorbox']))]);
$colorbox_dependencies .= '</div>';
$form['tracking']['linktracking']['matomo_trackcolorbox'] = [
@@ -259,7 +281,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
'#title' => $this->t('Track content in colorbox modal dialogs'),
'#description' => $this->t('Enable to track the content shown in colorbox modal windows.') . $colorbox_dependencies,
'#default_value' => $config->get('track.colorbox'),
'#disabled' => (\Drupal::moduleHandler()->moduleExists('colorbox') ? FALSE : TRUE),
'#disabled' => ($this->moduleHandler->moduleExists('colorbox') ? FALSE : TRUE),
];
// Message specific configurations.
@@ -288,7 +310,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
];
$site_search_dependencies = '<div class="admin-requirements">';
$site_search_dependencies .= $this->t('Requires: @module-list', ['@module-list' => (\Drupal::moduleHandler()->moduleExists('search') ? $this->t('@module (<span class="admin-enabled">enabled</span>)', ['@module' => 'Search']) : $this->t('@module (<span class="admin-missing">disabled</span>)', ['@module' => 'Search']))]);
$site_search_dependencies .= $this->t('Requires: @module-list', ['@module-list' => ($this->moduleHandler->moduleExists('search') ? $this->t('@module (<span class="admin-enabled">enabled</span>)', ['@module' => 'Search']) : $this->t('@module (<span class="admin-missing">disabled</span>)', ['@module' => 'Search']))]);
$site_search_dependencies .= '</div>';
$form['tracking']['search']['matomo_site_search'] = [
@@ -296,7 +318,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
'#title' => $this->t('Track internal search'),
'#description' => $this->t('If checked, internal search keywords are tracked.') . $site_search_dependencies,
'#default_value' => $config->get('track.site_search'),
'#disabled' => (\Drupal::moduleHandler()->moduleExists('search') ? FALSE : TRUE),
'#disabled' => ($this->moduleHandler->moduleExists('search') ? FALSE : TRUE),
];
// Privacy specific configurations.
@@ -308,7 +330,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
$form['tracking']['privacy']['matomo_privacy_donottrack'] = [
'#type' => 'checkbox',
'#title' => $this->t('Universal web tracking opt-out'),
'#description' => $this->t('If enabled and your Matomo server receives the <a href="http://donottrack.us/">Do-Not-Track</a> header from the client browser, the Matomo server will not track the user. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision.'),
'#description' => $this->t('If enabled and your Matomo server receives the <a href=":donottrack">Do-Not-Track</a> header from the client browser, the Matomo server will not track the user. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision.', [':donottrack' => 'https://www.eff.org/issues/do-not-track']),
'#default_value' => $config->get('privacy.donottrack'),
];
@@ -334,7 +356,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
// Custom variables.
$form['matomo_custom_var'] = [
'#description' => $this->t('You can add Matomos <a href=":custom_var_documentation">Custom Variables</a> here. These will be added to every page that Matomo tracking code appears on. Custom variable names and values are limited to 200 characters in length. Keep the names and values as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', [':custom_var_documentation' => 'http://matomo.org/docs/custom-variables/']),
'#description' => $this->t('You can add Matomos <a href=":custom_var_documentation">Custom Variables</a> here. These will be added to every page that Matomo tracking code appears on. Custom variable names and values are limited to 200 characters in length. Keep the names and values as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', [':custom_var_documentation' => 'https://matomo.org/docs/custom-variables/']),
'#title' => $this->t('Custom variables'),
'#tree' => TRUE,
'#type' => 'details',
@@ -382,7 +404,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
'#element_validate' => [[get_class($this), 'tokenElementValidate']],
'#token_types' => ['node'],
];
if (\Drupal::moduleHandler()->moduleExists('token')) {
if ($this->moduleHandler->moduleExists('token')) {
$form['matomo_custom_var']['slots'][$i]['value']['#element_validate'][] = 'token_element_validate';
}
$form['matomo_custom_var']['slots'][$i]['scope'] = [
@@ -402,7 +424,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
'#type' => 'item',
'#description' => $this->t("You can supplement Matomos' basic IP address tracking of visitors by segmenting users based on custom variables. Make sure you will not associate (or permit any third party to associate) any data gathered from your websites (or such third parties' websites) with any personally identifying information from any source as part of your use (or such third parties' use) of the Matomo' service."),
];
if (\Drupal::moduleHandler()->moduleExists('token')) {
if ($this->moduleHandler->moduleExists('token')) {
$form['matomo_custom_var']['matomo_custom_var_token_tree'] = [
'#theme' => 'token_tree_link',
'#token_types' => ['node'],
@@ -424,7 +446,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
];
// Allow for tracking of the originating node when viewing translation sets.
if (\Drupal::moduleHandler()->moduleExists('content_translation')) {
if ($this->moduleHandler->moduleExists('content_translation')) {
$form['advanced']['matomo_translation_set'] = [
'#type' => 'checkbox',
'#title' => $this->t('Track translation sets as one unit'),
@@ -439,7 +461,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
'#type' => 'details',
'#title' => $this->t('Custom JavaScript code'),
'#open' => TRUE,
'#description' => $this->t('You can add custom Matomo <a href=":snippets">code snippets</a> here. These will be added to every page that Matomo appears on. <strong>Do not include the &lt;script&gt; tags</strong>, and always end your code with a semicolon (;).', [':snippets' => 'http://matomo.org/docs/javascript-tracking/']),
'#description' => $this->t('You can add custom Matomo <a href=":snippets">code snippets</a> here. These will be added to every page that Matomo appears on. <strong>Do not include the &lt;script&gt; tags</strong>, and always end your code with a semicolon (;).', [':snippets' => 'https://matomo.org/docs/javascript-tracking/']),
];
$form['advanced']['codesnippet']['matomo_codesnippet_before'] = [
'#type' => 'textarea',
@@ -496,7 +518,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
$url = $form_state->getValue('matomo_url_http') . 'piwik.php';
try {
$result = \Drupal::httpClient()->get($url);
$result = $this->httpClient->get($url);
if ($result->getStatusCode() != 200 && $form_state->getValue('matomo_url_skiperror') == FALSE) {
$form_state->setErrorByName('matomo_url_http', $this->t('The validation of "@url" failed with error "@error" (HTTP code @code).', [
'@url' => UrlHelper::filterBadProtocol($url),
@@ -517,7 +539,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
if (!empty($matomo_url_https)) {
$url = $matomo_url_https . 'piwik.php';
try {
$result = \Drupal::httpClient()->get($url);
$result = $this->httpClient->get($url);
if ($result->getStatusCode() != 200 && $form_state->getValue('matomo_url_skiperror') == FALSE) {
$form_state->setErrorByName('matomo_url_https', $this->t('The validation of "@url" failed with error "@error" (HTTP code @code).', [
'@url' => UrlHelper::filterBadProtocol($url),
@@ -714,4 +736,17 @@ class MatomoAdminSettingsForm extends ConfigFormBase {
return preg_match('/' . implode('|', array_map('preg_quote', $token_blacklist)) . '/i', $token_string);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
// Load the service required to construct this class.
$container->get('config.factory'),
$container->get('current_user'),
$container->get('module_handler'),
$container->get('http_client')
);
}
}
@@ -0,0 +1,15 @@
<?php
namespace Drupal\matomo;
/**
* Provides an interface.
*/
interface MatomoInterface {
/**
* Define the default file extension list that should be tracked as download.
*/
const MATOMO_TRACKFILES_EXTENSIONS = '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip';
}
@@ -63,7 +63,6 @@ class MatomoSearchTest extends WebTestBase {
$search['keys'] = $this->randomMachineName(8);
// Create a node to search for.
// Create a node.
$edit = [];
$edit['title[0][value]'] = 'This is a test title';
$edit['body[0][value]'] = 'This test content contains ' . $search['keys'] . ' string.';
@@ -57,7 +57,11 @@ class MatomoStatusMessagesTest extends WebTestBase {
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Example error message with html tags and link."]);', '[testMatomoStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.');
// Enable logging of status, warnings and errors.
$this->config('matomo.settings')->set('track.messages', ['status' => 'status', 'warning' => 'warning', 'error' => 'error'])->save();
$this->config('matomo.settings')->set('track.messages', [
'status' => 'status',
'warning' => 'warning',
'error' => 'error',
])->save();
$this->drupalGet('matomo-test/drupal-messenger-add-message');
$this->assertRaw('_paq.push(["trackEvent", "Messages", "Status message", "Example status message."]);', '[testMatomoStatusMessages]: Example status message is enabled for tracking.');
@@ -4,8 +4,8 @@ description: 'Support module for Matomo testing.'
package: Testing
# core: 8.x
# Information added by Drupal.org packaging script on 2018-07-13
version: '8.x-1.7'
# Information added by Drupal.org packaging script on 2019-02-08
version: '8.x-1.9'
core: '8.x'
project: 'matomo'
datestamp: 1531470224
datestamp: 1549615102
@@ -17,10 +17,10 @@ class MatomoTestController extends ControllerBase {
*/
public function drupalAddMessageTest() {
// Set some messages.
drupal_set_message($this->t('Example status message.'), 'status');
drupal_set_message($this->t('Example warning message.'), 'warning');
drupal_set_message($this->t('Example error message.'), 'error');
drupal_set_message($this->t('Example error <em>message</em> with html tags and <a href="http://example.com/">link</a>.'), 'error');
$this->messenger()->addMessage($this->t('Example status message.'), 'status');
$this->messenger()->addMessage($this->t('Example warning message.'), 'warning');
$this->messenger()->addMessage($this->t('Example error message.'), 'error');
$this->messenger()->addMessage($this->t('Example error <em>message</em> with html tags and <a href="https://example.com/">link</a>.'), 'error');
return [];
}
@@ -1,13 +1,16 @@
name: Extended Path Aliases
type: module
description: 'Automatically extend path aliases to include tabs, like <em>about-us/edit</em> for <em>node/123/edit</em>.<br> Allow these aliases to be entered in page specification wild-cards, <em>about-us*</em>, e.g for block visibility.'
# core: 8.x
configure: path_alias_xt.settings_form
dependencies:
- path
# Information added by Drupal.org packaging script on 2017-07-14
type: module
# core: 8.x
dependencies:
- drupal:path
configure: path_alias_xt.settings_form
# Information added by Drupal.org packaging script on 2019-01-30
version: '8.x-1.x-dev'
core: '8.x'
project: 'path_alias_xt'
datestamp: 1500044946
datestamp: 1548876484
@@ -57,7 +57,7 @@ class PathAliasXtProcessorAlias extends PathProcessorAlias {
// Validate the path.
// Injecting the service threw ServiceCircularReferenceException.
if (\Drupal::service('path.validator')->isValid($return_path)) {
if (\Drupal::service('path.validator')->getUrlIfValidWithoutAccessCheck($return_path)) {
return $return_path;
}
}
@@ -1,3 +1,61 @@
CONTENTS OF THIS FILE
---------------------
This is the new module home for a unified redirection API (also replaces
* Introduction
* Requirements
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Redirect module provides a unified redirection API (also replaces
path_redirect and globalredirect).
* For a full description of the module visit:
https://www.drupal.org/project/redirect
* To submit bug reports and feature suggestions, or to track changes visit:
https://www.drupal.org/project/issues/redirect
REQUIREMENTS
------------
This module requires no modules outside of Drupal core.
INSTALLATION
------------
* Install the Redirect module as you would normally install a contributed
Drupal module. Visit https://www.drupal.org/node/1897420 for further
information.
CONFIGURATION
-------------
1. Navigate to Administration > Extend and enable the module.
2. Navigate to Administration > Configuration > Search and Metadata > URL
redirects for configuration.
3. Select "Add redirect" and in the "Path" field add the old path.
4. In the "To" field, start typing the title of a piece of content to select
it. You can also enter an internal path such as /node/add or an external
URL such as http://example.com. Enter <front> to link to the front page.
5. Select the Redirect status: 300 Multiple Choices, 301 Moved Permanently,
302 Found, 303 See Other, 304 Not Modified, 305 Use Proxy, or 307
Temporary Redirect. Save.
6. Once a redirect has been added, it will be listed in the URL Redirects
vertical tab group on the content's edit page.
MAINTAINERS
-----------
Supporting organization for 8.x-1.x port:
* MD Systems - https://www.drupal.org/md-systems
@@ -38,7 +38,7 @@ display:
exposed_form:
type: basic
options:
submit_button: Apply
submit_button: Filter
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
@@ -7,8 +7,8 @@ dependencies:
- redirect
- views
# Information added by Drupal.org packaging script on 2018-05-04
version: '8.x-1.2'
# Information added by Drupal.org packaging script on 2018-10-16
version: '8.x-1.3'
core: '8.x'
project: 'redirect'
datestamp: 1525419807
datestamp: 1539682690
@@ -2,7 +2,6 @@
namespace Drupal\redirect_404\EventSubscriber;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Path\PathMatcherInterface;
@@ -106,11 +105,11 @@ class Redirect404Subscriber implements EventSubscriberInterface {
$path = $this->currentPath->getPath();
// Ignore paths specified in the redirect settings.
if ($pages = Unicode::strtolower($this->config->get('pages'))) {
if ($pages = mb_strtolower($this->config->get('pages'))) {
// Do not trim a trailing slash if that is the complete path.
$path_to_match = $path === '/' ? $path : rtrim($path, '/');
if ($this->pathMatcher->matchPath(Unicode::strtolower($path_to_match), $pages)) {
if ($this->pathMatcher->matchPath(mb_strtolower($path_to_match), $pages)) {
return;
}
}
@@ -51,7 +51,7 @@ class SqlRedirectNotFoundStorage implements RedirectNotFoundStorageInterface {
* {@inheritdoc}
*/
public function logRequest($path, $langcode) {
if (Unicode::strlen($path) > static::MAX_PATH_LENGTH) {
if (mb_strlen($path) > static::MAX_PATH_LENGTH) {
// Don't attempt to log paths that would result in an exception. There is
// no point in logging truncated paths, as they cannot be used to build a
// new redirect.
@@ -2,6 +2,7 @@
namespace Drupal\redirect_404\Tests;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
@@ -88,14 +89,14 @@ class Fix404RedirectUILanguageTest extends Redirect404TestBase {
// Check if we generate correct Add redirect url and if the form is
// pre-filled.
$destination = Url::fromRoute('redirect_404.fix_404')->getInternalPath();
$options = [
'query' => [
'source' => 'testing',
'language' => 'fr',
'destination' => $destination,
]
$expected_query = [
'destination' => $destination,
'language' => 'fr',
'source' => 'testing',
];
$this->assertUrl('admin/config/search/redirect/add', $options);
$parsed_url = UrlHelper::parse($this->getUrl());
$this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']);
$this->assertEqual($expected_query, $parsed_url['query']);
$this->assertFieldByName('redirect_source[0][path]', 'testing');
$this->assertOptionSelected('edit-language-0-value', 'fr');
// Save the redirect.
@@ -177,14 +178,14 @@ class Fix404RedirectUILanguageTest extends Redirect404TestBase {
// Assign a redirect to 'testing1'.
$this->clickLink('Add redirect');
$options = [
'query' => [
'source' => 'testing1',
'language' => 'en',
'destination' => $destination,
]
$expected_query = [
'destination' => $destination,
'language' => 'en',
'source' => 'testing1',
];
$this->assertUrl('admin/config/search/redirect/add', $options);
$parsed_url = UrlHelper::parse($this->getUrl());
$this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']);
$this->assertEqual($expected_query, $parsed_url['query']);
$this->assertFieldByName('redirect_source[0][path]', 'testing1');
$this->assertOptionSelected('edit-language-0-value', 'en');
$edit = ['redirect_redirect[0][uri]' => '/node'];
@@ -2,6 +2,7 @@
namespace Drupal\redirect_404\Tests;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Url;
/**
@@ -26,14 +27,14 @@ class Fix404RedirectUITest extends Redirect404TestBase {
// Check if we generate correct Add redirect url and if the form is
// pre-filled.
$destination = Url::fromRoute('redirect_404.fix_404')->getInternalPath();
$options = [
'query' => [
'source' => 'non-existing0',
'language' => 'en',
'destination' => $destination,
]
$expected_query = [
'destination' => $destination,
'language' => 'en',
'source' => 'non-existing0',
];
$this->assertUrl('admin/config/search/redirect/add', $options);
$parsed_url = UrlHelper::parse($this->getUrl());
$this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']);
$this->assertEqual($expected_query, $parsed_url['query']);
$this->assertFieldByName('redirect_source[0][path]', 'non-existing0');
// Save the redirect.
$edit = ['redirect_redirect[0][uri]' => '/node'];
@@ -97,14 +98,14 @@ class Fix404RedirectUITest extends Redirect404TestBase {
// Assign a redirect to 'non-existing2'.
$this->clickLink('Add redirect');
$options = [
'query' => [
'source' => 'non-existing2',
'language' => 'en',
'destination' => $destination,
]
$expected_query = [
'source' => 'non-existing2',
'language' => 'en',
'destination' => $destination,
];
$this->assertUrl('admin/config/search/redirect/add', $options);
$parsed_url = UrlHelper::parse($this->getUrl());
$this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']);
$this->assertEqual($expected_query, $parsed_url['query']);
$this->assertFieldByName('redirect_source[0][path]', 'non-existing2');
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertUrl('admin/config/search/redirect/404');

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