updated contrib modules
This commit is contained in:
@@ -1,7 +1,30 @@
|
||||
Admin Toolbar 8.1.22, 2017-XX-XX
|
||||
Admin Toolbar 8.1.23, 2018-02-06
|
||||
--------------------------------
|
||||
Changes since 8.1.22:
|
||||
|
||||
- #2924266 by sunlix, romainj, adriancid, samerali: Add a menu link for the
|
||||
media module.
|
||||
- #2941184 by daniel.nitsche, adriancid: Remove unwanted comment from
|
||||
admin.toolbar.css.
|
||||
- #2912503 by Amsteri, m.abdulqader: Right to left language direction support.
|
||||
- #2937982 by romainj, adriancid: Auto-generation of menu links conflicts with
|
||||
other modules.
|
||||
- #2932873 by vaplas, saravanaprasanth, adriancid: Horizontal mode only works if
|
||||
the page is loaded in horizontal mode.
|
||||
- #2935311 by romainj, adriancid: Use the $entityTypeManager variable instead of
|
||||
the \Drupal::entityTypeManager service.
|
||||
- #2935449 by adriancid: Add the release information for the 1.22 version in the
|
||||
CHANGELOG.txt.
|
||||
- #2932476 by romainj: toolbar.tree library should have a dependency to the
|
||||
core/drupal library.
|
||||
- #2931503 by K3vin_nl: Admin toolbar generates invalid class names.
|
||||
|
||||
Admin Toolbar 8.1.22, 2018-01-02
|
||||
--------------------------------
|
||||
Changes since 8.1.21:
|
||||
|
||||
- #2929053 by kkuhnen, eme, adriancid: admin_toolbar.js should use Drupal
|
||||
behaviors.
|
||||
- #2929061 by romainj, dsnopek, adriancid: admin_toolbar_links_access_filter
|
||||
doesn't do anything unless admin_toolbar is enabled.
|
||||
- #2928836 by eme: One extra pixel line under the admin toolbar.
|
||||
|
||||
@@ -8,8 +8,8 @@ type: module
|
||||
dependencies:
|
||||
- drupal:toolbar
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-01-02
|
||||
version: '8.x-1.22'
|
||||
# Information added by Drupal.org packaging script on 2018-02-06
|
||||
version: '8.x-1.23'
|
||||
core: '8.x'
|
||||
project: 'admin_toolbar'
|
||||
datestamp: 1514888588
|
||||
datestamp: 1517936588
|
||||
|
||||
@@ -7,3 +7,4 @@ toolbar.tree:
|
||||
js/admin_toolbar.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use Drupal\Core\Menu\MenuTreeParameters;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Component\Utility\Html;
|
||||
|
||||
/**
|
||||
* Implements hook_toolbar_alter().
|
||||
@@ -90,7 +91,7 @@ function toolbar_tools_menu_navigation_links(array $tree) {
|
||||
|
||||
$element->options['attributes']['class'][] = 'toolbar-icon';
|
||||
$string = strtolower(str_replace(['.', ' ', '_'], ['-', '-', '-'], $definition['id']));
|
||||
$element->options['attributes']['class'][] = 'toolbar-icon-' . $string;
|
||||
$element->options['attributes']['class'][] = Html::cleanCssIdentifier('toolbar-icon-' . $string);
|
||||
$element->options['attributes']['title'] = $link->getDescription();
|
||||
}
|
||||
return $tree;
|
||||
|
||||
+7
-5
@@ -1,13 +1,15 @@
|
||||
name: Admin Toolbar Links Access Filter
|
||||
description: Provides a workaround for the common problem that users with 'Use the administration pages and help' permission see menu links they don't have access permission for. Once the issue <a href='https://www.drupal.org/node/296693'>https://www.drupal.org/node/296693</a> be solved, this module will be deprecated.
|
||||
package: Administration
|
||||
dependencies:
|
||||
- admin_toolbar:admin_toolbar
|
||||
|
||||
type: module
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-01-02
|
||||
version: '8.x-1.22'
|
||||
dependencies:
|
||||
- admin_toolbar:admin_toolbar
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-02-06
|
||||
version: '8.x-1.23'
|
||||
core: '8.x'
|
||||
project: 'admin_toolbar'
|
||||
datestamp: 1514888588
|
||||
datestamp: 1517936588
|
||||
|
||||
+3
-3
@@ -8,8 +8,8 @@ type: module
|
||||
dependencies:
|
||||
- admin_toolbar:admin_toolbar
|
||||
|
||||
# Information added by Drupal.org packaging script on 2018-01-02
|
||||
version: '8.x-1.22'
|
||||
# Information added by Drupal.org packaging script on 2018-02-06
|
||||
version: '8.x-1.23'
|
||||
core: '8.x'
|
||||
project: 'admin_toolbar'
|
||||
datestamp: 1514888588
|
||||
datestamp: 1517936588
|
||||
|
||||
+60
-116
@@ -273,7 +273,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
'weight' => -5,
|
||||
];
|
||||
// Add node links for each content type.
|
||||
foreach (\Drupal::entityTypeManager()->getStorage('node_type')->loadMultiple() as $type) {
|
||||
foreach ($entityTypeManager->getStorage('node_type')->loadMultiple() as $type) {
|
||||
$links['node.add.' . $type->id()] = [
|
||||
'title' => t($type->label()),
|
||||
'route_name' => 'node.add',
|
||||
@@ -418,7 +418,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
'weight' => '-1',
|
||||
];
|
||||
if ($moduleHandler->moduleExists('webprofiler')) {
|
||||
$links['admin_menu_tools.devel.webprofiler'] = [
|
||||
$links['admin_toolbar_tools.devel.webprofiler'] = [
|
||||
'title' => t('Web Profiler settings'),
|
||||
'route_name' => 'webprofiler.settings',
|
||||
'menu_name' => 'admin',
|
||||
@@ -478,20 +478,33 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'admin_development',
|
||||
];
|
||||
// Menu link for the Toolbar module.
|
||||
$links['admin_toolbar_tools.toolbar.settings'] = [
|
||||
'title' => t('Toolbar settings'),
|
||||
'route_name' => 'devel.toolbar.settings_form',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'devel.admin_settings',
|
||||
];
|
||||
}
|
||||
|
||||
// If module Views Ui enabled.
|
||||
if ($moduleHandler->moduleExists('views_ui')) {
|
||||
$links['views_ui.add'] = [
|
||||
$links['admin_toolbar_tools.views_ui.add'] = [
|
||||
'title' => t('Add new view'),
|
||||
'route_name' => 'views_ui.add',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'entity.view.collection',
|
||||
'weight' => -5,
|
||||
];
|
||||
$links['admin_toolbar_tools.views_ui.field_list'] = [
|
||||
'title' => t('Used in views'),
|
||||
'route_name' => 'views_ui.reports_fields',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'entity.field_storage_config.collection',
|
||||
];
|
||||
}
|
||||
|
||||
$links['system.theme_settings_'] = [
|
||||
$links['admin_toolbar_tools.system.theme_settings'] = [
|
||||
'title' => t('Settings'),
|
||||
'route_name' => 'system.theme_settings',
|
||||
'menu_name' => 'admin',
|
||||
@@ -499,13 +512,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
];
|
||||
|
||||
if ($moduleHandler->moduleExists('webprofiler')) {
|
||||
$links['admin_menu_tools.devel.webprofiler'] = [
|
||||
$links['admin_toolbar_tools.devel.webprofiler'] = [
|
||||
'title' => t('Webprofiler settings'),
|
||||
'route_name' => 'webprofiler.settings',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'admin_development',
|
||||
];
|
||||
}
|
||||
|
||||
if ($moduleHandler->moduleExists('update')) {
|
||||
$links['update.theme_install_'] = [
|
||||
'title' => t('Install new theme'),
|
||||
@@ -534,122 +548,52 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add menu links for local tasks that sit below any admin menu link routes.
|
||||
$manager = \Drupal::service('plugin.manager.menu.local_task');
|
||||
foreach ($links as $key => $link) {
|
||||
// If module Language enabled.
|
||||
if ($moduleHandler->moduleExists('language')) {
|
||||
$links['admin_toolbar_tools.language.negotiation'] = [
|
||||
'title' => t('Detection and selection'),
|
||||
'route_name' => 'language.negotiation',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'entity.configurable_language.collection',
|
||||
];
|
||||
}
|
||||
|
||||
// Ignore menu links that have no route name.
|
||||
if (empty($link['route_name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore links that are not in the admin menu, include parents in the
|
||||
// check as they inherit the menu name from the parent if not set.
|
||||
$link_to_check = $link;
|
||||
do {
|
||||
// @todo Use configuration for admin menu name once added in
|
||||
// https://www.drupal.org/node/1869638.
|
||||
if (isset($link_to_check['menu_name'])) {
|
||||
// If the link belongs to the admin menu, then skip this loop and
|
||||
// look for local tasks.
|
||||
if ($link_to_check['menu_name'] === 'admin') {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// If it is explicitly not in the admin menu, skip.
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($link_to_check['parent']) && isset($links[$link_to_check['parent']])) {
|
||||
$link_to_check = $links[$link_to_check['parent']];
|
||||
}
|
||||
else {
|
||||
// No parent and we found no menu_name, skip.
|
||||
continue 2;
|
||||
}
|
||||
} while ($link_to_check);
|
||||
|
||||
$route = $link['route_name'];
|
||||
$route_local_tasks = $manager->getLocalTasksForRoute($route);
|
||||
if (empty($route_local_tasks[0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($route_local_tasks[0] as $task_plugin) {
|
||||
$definition = $task_plugin->getPluginDefinition();
|
||||
$local_route = $definition['route_name'];
|
||||
if (empty($local_route)) {
|
||||
continue;
|
||||
}
|
||||
$exists = FALSE;
|
||||
$param = FALSE;
|
||||
if (isset($link['route_parameters'])) {
|
||||
$param = $link['route_parameters'];
|
||||
}
|
||||
// Check if local task has an already registered link in toolbar.
|
||||
foreach ($links as $link2) {
|
||||
// Route_name that could be a 'class'.
|
||||
if (!(isset($link2['route_name']) && ($link2['route_name'] == $local_route))) {
|
||||
continue;
|
||||
}
|
||||
// If route are equal, the parameters could be different.
|
||||
// @todo: check if this is true.
|
||||
if ($param) {
|
||||
if (isset($link2['route_parameters']) && ($param == $link2['route_parameters'])) {
|
||||
$exists = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$exists = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$parent = FALSE;
|
||||
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = $definition['title'];
|
||||
$title = $param ? $title . ' ' . $link['title'] : $title;
|
||||
|
||||
// The following code is to choose the parent of the added link.
|
||||
// If the parent of the current tab also has the tab as local task
|
||||
// for example: "Content" has "File" as task" and and
|
||||
// "Content>Comment" also has "File" as a tab then we decide to
|
||||
// set this one as parent of the added link if not the current tab
|
||||
// will be the parent.
|
||||
if (!empty($link['parent']) && isset($links[$link['parent']])) {
|
||||
$parentlink = $links[$link['parent']];
|
||||
$parentlink_route = $parentlink['route_name'];
|
||||
$parent_primary = $manager->getLocalTasks($parentlink_route, 0);
|
||||
foreach ($parent_primary['tabs'] as $parent_tab) {
|
||||
if ($parent_tab['#link']['url']) {
|
||||
$parent_route = $parent_tab['#link']['url']->getRouteName();
|
||||
if ($parent_route == $local_route) {
|
||||
$parent = $link['parent'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$parent = $parent ? $parent : $key;
|
||||
// Make sure the key of the route is unique.
|
||||
$links[$key . '_' . $local_route] = [
|
||||
'title' => $title,
|
||||
'route_name' => $local_route,
|
||||
'menu_name' => 'admin',
|
||||
'parent' => $parent,
|
||||
'att' => TRUE,
|
||||
// If module Media enabled.
|
||||
if ($moduleHandler->moduleExists('media')) {
|
||||
$links['admin_toolbar_tools.add_media'] = [
|
||||
'title' => t('Add media'),
|
||||
'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) {
|
||||
$links['node.add.' . $type->id()] = [
|
||||
'title' => t($type->label()),
|
||||
'route_name' => 'entity.media.add_form',
|
||||
'parent' => 'admin_toolbar_tools.add_media',
|
||||
'route_parameters' => ['media_type' => $type->id()],
|
||||
];
|
||||
if ($param) {
|
||||
$links[$key . '_' . $local_route]['route_parameters'] = $param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If module Config enabled.
|
||||
if ($moduleHandler->moduleExists('config')) {
|
||||
$links['admin_toolbar_tools.config.import'] = [
|
||||
'title' => t('Import'),
|
||||
'route_name' => 'config.import_full',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'config.sync',
|
||||
'weight' => 1,
|
||||
];
|
||||
$links['admin_toolbar_tools.config.export'] = [
|
||||
'title' => t('Export'),
|
||||
'route_name' => 'config.export_full',
|
||||
'menu_name' => 'admin',
|
||||
'parent' => 'config.sync',
|
||||
'weight' => 2,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/*---------------------- menu horizontal hover---- Krout Fethi FrontEnd Developer-----*/
|
||||
.toolbar-tray-horizontal .menu-item:hover {
|
||||
background: #fff;
|
||||
}
|
||||
@@ -105,32 +104,79 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal .toolbar .level-2 > ul {
|
||||
[dir="rtl"] .toolbar-tray-horizontal .menu-item:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal .menu-item a:focus {
|
||||
background: #abeae4;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal .toolbar-menu:not(:first-child) li.menu-item--expanded > a:focus {
|
||||
background-position: center right;
|
||||
background-image: url('../misc/icons/0074bd/chevron-right.svg');
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal .menu-item--expanded .menu {
|
||||
background: #fff;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal .menu-item--expanded {
|
||||
background-color: #f5f5f2;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal ul li li.menu-item {
|
||||
border-top: none transparent;
|
||||
border-right: 1px solid #dddddd;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
border-left: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar .toolbar-tray-horizontal .menu-item:last-child {
|
||||
border-left: 1px solid #dddddd;
|
||||
border-right: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar .toolbar-tray-horizontal ul ul li.menu-item:first-child {
|
||||
border-top: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul,
|
||||
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul,
|
||||
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul ul,
|
||||
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul ul ul ul ul {
|
||||
display: none;
|
||||
left: -999em; /* LTR */
|
||||
}
|
||||
|
||||
/* Lists nested under hovered list items */
|
||||
[dir="rtl"] .toolbar-tray-horizontal li.menu-item--expanded.hover-intent ul,
|
||||
[dir="rtl"] .toolbar-tray-horizontal li li.menu-item--expanded.hover-intent ul,
|
||||
[dir="rtl"] .toolbar-tray-horizontal li li li.menu-item--expanded.hover-intent ul,
|
||||
[dir="rtl"] .toolbar-tray-horizontal li li li li.menu-item--expanded.hover-intent ul,
|
||||
[dir="rtl"] .toolbar-tray-horizontal li li li li li.menu-item--expanded.hover-intent ul {
|
||||
display: block;
|
||||
left: auto; /* LTR */
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal .menu ul li a,
|
||||
[dir="rtl"] .toolbar-tray-horizontal .menu ul .toolbar-icon {
|
||||
padding: 12px 15px 12px 12px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded.hover-intent ul {
|
||||
display: block;
|
||||
position: absolute;
|
||||
padding-top: 0;
|
||||
top: 0;
|
||||
right: 200px;
|
||||
width: 200px;
|
||||
box-shadow: 2px 2px 3px hsla(0, 0%, 0%, 0.4);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal li:hover ul li {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal li:focus ul li {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar .toolbar-menu ul .toolbar-icon {
|
||||
padding-left: 1.3333em;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:hover ul {
|
||||
margin: -40px 197px 0 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:focus ul {
|
||||
margin: -40px 197px 0 0;
|
||||
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item > ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded ul li.menu-item--expanded {
|
||||
@@ -138,3 +184,28 @@
|
||||
background-image: url('../misc/icons/0074bd/chevron-left.svg');
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item.hover-intent ul {
|
||||
display: block;
|
||||
margin: -40px 197px 0 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal li:hover ul li {
|
||||
float: none;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal li.hover-intent ul li {
|
||||
float: none;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar-tray-horizontal .toolbar .level-2 > ul {
|
||||
position: absolute;
|
||||
padding-top: 0;
|
||||
top: 0;
|
||||
left: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .toolbar .toolbar-tray-vertical li.open > ul.toolbar-menu.clearfix {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
(function ($) {
|
||||
(function ($, Drupal) {
|
||||
Drupal.behaviors.adminToolbar = {
|
||||
attach: function (context, settings) {
|
||||
|
||||
$('a.toolbar-icon', context).removeAttr('title');
|
||||
|
||||
$('.toolbar-tray-horizontal li.menu-item--expanded, .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item', context).hoverIntent({
|
||||
|
||||
$('.toolbar-tray li.menu-item--expanded, .toolbar-tray ul li.menu-item--expanded .menu-item', context).hoverIntent({
|
||||
over: function () {
|
||||
// At the current depth, we should delete all "hover-intent" classes.
|
||||
// Other wise we get unwanted behaviour where menu items are expanded while already in hovering other ones.
|
||||
@@ -16,13 +16,13 @@
|
||||
},
|
||||
timeout: 250
|
||||
});
|
||||
|
||||
|
||||
// Make the toolbar menu navigable with keyboard.
|
||||
$('ul.toolbar-menu li.menu-item--expanded a', context).on('focusin', function () {
|
||||
$('li.menu-item--expanded', context).removeClass('hover-intent');
|
||||
$(this).parents('li.menu-item--expanded').addClass('hover-intent');
|
||||
});
|
||||
|
||||
|
||||
$('ul.toolbar-menu li.menu-item a', context).keydown(function (e) {
|
||||
if ((e.shiftKey && (e.keyCode || e.which) == 9)) {
|
||||
if ($(this).parent('.menu-item').prev().hasClass('menu-item--expanded')) {
|
||||
@@ -30,15 +30,15 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('.toolbar-menu:first-child > .menu-item:not(.menu-item--expanded) a, .toolbar-tab > a', context).on('focusin', function () {
|
||||
$('.menu-item--expanded').removeClass('hover-intent');
|
||||
});
|
||||
|
||||
|
||||
$('.toolbar-menu:first-child > .menu-item', context).on('hover', function () {
|
||||
$(this,'a').css("background: #fff;");
|
||||
$(this, 'a').css("background: #fff;");
|
||||
});
|
||||
|
||||
|
||||
$('ul:not(.toolbar-menu)', context).on({
|
||||
mousemove: function () {
|
||||
$('li.menu-item--expanded').removeClass('hover-intent');
|
||||
@@ -50,4 +50,4 @@
|
||||
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
||||
})(jQuery, Drupal);
|
||||
|
||||
@@ -4,6 +4,8 @@ sudo: false
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- env: DRUPAL=8.4.x
|
||||
php: 7.1
|
||||
- env: DRUPAL=8.4.x
|
||||
php: 7.0
|
||||
- env: DRUPAL=8.4.x
|
||||
|
||||
@@ -13,6 +13,8 @@ Changelog
|
||||
23-APR-2017 8.x-1.0-alpha9
|
||||
01-DEC-2017 8.x-1.0-alpha10
|
||||
19-DEC-2017 8.x-1.0-alpha11
|
||||
12-FEB-2018 8.x-1.0-alpha12
|
||||
07-MAR-2018 8.x.1.0-alpha13
|
||||
|
||||
Status
|
||||
====
|
||||
@@ -108,13 +110,17 @@ marked with [x] are considered complete.
|
||||
- [x] Check loader logic in Domain Access `node_access`
|
||||
- [x] Check id logic in Domain Alias list controller
|
||||
- [x] Check domain responses on configuration forms
|
||||
- [ ] Remove deprecated `entity_get_form_display`
|
||||
- [ ] Implement theme functions or twig templates where proper
|
||||
- [x] Remove deprecated `entity_get_form_display`
|
||||
- [x] Implement theme functions or twig templates where proper
|
||||
- [ ] Advanced drush integration / complete labelled tasks
|
||||
- [ ] Add filter options to domain_access and domain_source views
|
||||
- [ ] Test cron handling
|
||||
- [ ] Caching strategies in DomainNegotiator
|
||||
- [ ] Caching strategies in DomainConfigOverrides
|
||||
- [ ] Cache in the DomainAccessManager
|
||||
- [ ] Add filter options to domain_access and domain_source views
|
||||
- [ ] Proper handling of default node values
|
||||
- [ ] Do not allow actions to be edited?
|
||||
- [o] Recreate the Domain Theme module -- see https://www.drupal.org/project/domain_theme_switch
|
||||
|
||||
# Final
|
||||
@@ -124,3 +130,4 @@ marked with [x] are considered complete.
|
||||
- [x] Remove calls to deprecated methods / classes
|
||||
- [ ] Remove unnecessary use statements
|
||||
- [ ] Support Tour module
|
||||
- [ ] Views schema fails -- see https://www.drupal.org/project/drupal/issues/2834801
|
||||
|
||||
@@ -8,8 +8,8 @@ dependencies:
|
||||
- options
|
||||
configure: domain.admin
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
@@ -27,12 +27,11 @@ function domain_update_8001() {
|
||||
* Configures user form display to checkboxes widget for domain admin field.
|
||||
*/
|
||||
function _domain_configure_field() {
|
||||
// @TODO: This function is deprecated, but using the OO syntax is causing
|
||||
// test fails.
|
||||
entity_get_form_display('user', 'user', 'default')
|
||||
->setComponent(DOMAIN_ADMIN_FIELD, array(
|
||||
if ($display = \Drupal::entityTypeManager()->getStorage('entity_form_display')->load('user.user.default')) {
|
||||
$display->setComponent(DOMAIN_ADMIN_FIELD, [
|
||||
'type' => 'options_buttons',
|
||||
'weight' => 50,
|
||||
))
|
||||
])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -15,3 +15,4 @@ process:
|
||||
status: valid
|
||||
destination:
|
||||
plugin: entity:domain
|
||||
destination_module: domain
|
||||
@@ -188,7 +188,6 @@ class DomainElementManager implements DomainElementManagerInterface {
|
||||
*/
|
||||
public function listDisallowed(array $disallowed) {
|
||||
$domains = $this->domainStorage->loadMultiple($disallowed);
|
||||
// @TODO: Proper theme function here.
|
||||
$string = $this->t('The following domains are currently assigned and cannot be changed:');
|
||||
foreach ($domains as $domain) {
|
||||
$items[] = $domain->label();
|
||||
|
||||
@@ -204,7 +204,7 @@ class DomainNegotiator implements DomainNegotiatorInterface {
|
||||
return TRUE;
|
||||
}
|
||||
// Check for registered alias matches.
|
||||
$values = array('hostname' => $httpHost);
|
||||
$values = array('hostname' => $hostname);
|
||||
/** @var \Drupal\domain\Entity\DomainInterface $domain */
|
||||
$domain = $this->domainStorage->create($values);
|
||||
$domain->setMatchType(self::DOMAIN_MATCH_NONE);
|
||||
|
||||
+21
-2
@@ -27,8 +27,6 @@ class DomainServerBlock extends DomainBlockBase {
|
||||
|
||||
/**
|
||||
* Build the output.
|
||||
*
|
||||
* @TODO: abstract or theme this function?
|
||||
*/
|
||||
public function build() {
|
||||
/** @var \Drupal\domain\DomainInterface $domain */
|
||||
@@ -47,10 +45,14 @@ class DomainServerBlock extends DomainBlockBase {
|
||||
$domain->getResponse();
|
||||
$check = \Drupal::service('entity_type.manager')->getStorage('domain')->loadByHostname($_SERVER['HTTP_HOST']);
|
||||
$match = $this->t('Exact match');
|
||||
// This value is not translatable.
|
||||
$environment = 'default';
|
||||
if (!$check) {
|
||||
// Specific check for Domain Alias.
|
||||
if (isset($domain->alias)) {
|
||||
$match = $this->t('ALIAS: Using alias %id', array('%id' => $domain->alias->getPattern()));
|
||||
// Get the environment.
|
||||
$environment = $domain->alias->getEnvironment();
|
||||
}
|
||||
else {
|
||||
$match = $this->t('FALSE: Using default domain.');
|
||||
@@ -60,6 +62,23 @@ class DomainServerBlock extends DomainBlockBase {
|
||||
$this->t('Domain match'),
|
||||
$match,
|
||||
);
|
||||
$rows[] = [
|
||||
$this->t('Environment'),
|
||||
$environment,
|
||||
];
|
||||
$rows[] = [
|
||||
$this->t('Canonical hostname'),
|
||||
$domain->getCanonical(),
|
||||
];
|
||||
$rows[] = [
|
||||
$this->t('Base path'),
|
||||
$domain->getPath(),
|
||||
];
|
||||
$rows[] = [
|
||||
$this->t('Current URL'),
|
||||
$domain->getUrl(),
|
||||
];
|
||||
|
||||
$www = \Drupal::config('domain.settings')->get('www_prefix');
|
||||
$rows[] = array(
|
||||
$this->t('Strip www prefix'),
|
||||
|
||||
@@ -25,8 +25,6 @@ class DomainSwitcherBlock extends DomainBlockBase {
|
||||
|
||||
/**
|
||||
* Build the output.
|
||||
*
|
||||
* @TODO: abstract or theme this function?
|
||||
*/
|
||||
public function build() {
|
||||
/** @var \Drupal\domain\DomainInterface $active_domain */
|
||||
|
||||
@@ -27,8 +27,6 @@ class DomainTokenBlock extends DomainBlockBase {
|
||||
|
||||
/**
|
||||
* Build the output.
|
||||
*
|
||||
* @TODO: abstract or theme this function?
|
||||
*/
|
||||
public function build() {
|
||||
/** @var \Drupal\domain\DomainInterface $domain */
|
||||
@@ -49,7 +47,7 @@ class DomainTokenBlock extends DomainBlockBase {
|
||||
/**
|
||||
* Generates available tokens for printing.
|
||||
*
|
||||
* @param Drupal\domain\DomainInterface $domain
|
||||
* @param \Drupal\domain\DomainInterface $domain
|
||||
* The active domain request.
|
||||
* @return array
|
||||
* An array keyed by token name, with value of replacement value.
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@ use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
* Drupal 7 Domain source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_domain"
|
||||
* id = "d7_domain",
|
||||
* source_module = "domain"
|
||||
* )
|
||||
*/
|
||||
class DomainRecord extends DrupalSqlBase {
|
||||
|
||||
+3
-3
@@ -9,8 +9,8 @@ hidden: TRUE
|
||||
dependencies:
|
||||
- domain
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
+3
-5
@@ -150,11 +150,9 @@ class DomainEntityReferenceTest extends DomainTestBase {
|
||||
$field_config->save();
|
||||
|
||||
// Tell the form system how to behave.
|
||||
entity_get_form_display('node', 'article', 'default')
|
||||
->setComponent($name, array(
|
||||
'type' => 'options_buttons',
|
||||
))
|
||||
->save();
|
||||
if ($display = \Drupal::entityTypeManager()->getStorage('entity_form_display')->load('node.article.default')) {
|
||||
$display->setComponent($name, ['type' => 'options_buttons'])->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Schema for the domain access plugins.
|
||||
|
||||
views.access.domain_access_admin:
|
||||
type: mapping
|
||||
label: 'Domain Access: Administer domain editors'
|
||||
views.access.domain_access_editor:
|
||||
type: mapping
|
||||
label: 'Domain Access: Edit domain content'
|
||||
views.argument.domain_access_argument:
|
||||
type: views_argument
|
||||
label: 'Domain Access'
|
||||
views.field.domain_access_field:
|
||||
type: views_field
|
||||
label: 'Domain Access'
|
||||
mapping:
|
||||
click_sort_column:
|
||||
type: string
|
||||
label: 'Column used for click sorting'
|
||||
type:
|
||||
type: string
|
||||
label: 'Formatter'
|
||||
settings:
|
||||
label: 'Settings'
|
||||
type: field.formatter.settings.[%parent.type]
|
||||
group_column:
|
||||
type: string
|
||||
label: 'Group by column'
|
||||
group_columns:
|
||||
type: sequence
|
||||
label: 'Group by columns'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Column'
|
||||
group_rows:
|
||||
type: boolean
|
||||
label: 'Display all values in the same row'
|
||||
delta_limit:
|
||||
type: integer
|
||||
label: 'Field'
|
||||
delta_offset:
|
||||
type: integer
|
||||
label: 'Offset'
|
||||
delta_reversed:
|
||||
type: boolean
|
||||
label: 'Reversed'
|
||||
delta_first_last:
|
||||
type: boolean
|
||||
label: 'First and last only'
|
||||
multi_type:
|
||||
type: string
|
||||
label: 'Display type'
|
||||
separator:
|
||||
type: label
|
||||
label: 'Separator'
|
||||
field_api_classes:
|
||||
type: boolean
|
||||
label: 'Use field template'
|
||||
views.filter.domain_access_current_all_filter:
|
||||
type: views_filter
|
||||
label: 'Current Domain or All Domains'
|
||||
views.filter.domain_access_filter:
|
||||
type: views.filter.in_operator
|
||||
label: 'Domain Access'
|
||||
@@ -8,8 +8,8 @@ dependencies:
|
||||
- domain
|
||||
- node
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
@@ -343,7 +343,7 @@ function domain_access_node_access(NodeInterface $node, $op, AccountInterface $a
|
||||
}
|
||||
// Check to see that we have a valid active domain.
|
||||
// Without one, we cannot assert an opinion about access.
|
||||
if (empty($active_domain->getDomainId())) {
|
||||
if (!$active_domain || empty($active_domain->getDomainId())) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
@@ -560,19 +560,16 @@ function domain_access_confirm_fields($entity_type, $bundle, $text = array()) {
|
||||
$field_config->save();
|
||||
}
|
||||
// Tell the form system how to behave. Default to radio buttons.
|
||||
// @TODO: This function is deprecated, but using the OO syntax is causing
|
||||
// test fails.
|
||||
entity_get_form_display($entity_type, $bundle, 'default')
|
||||
->setComponent(DOMAIN_ACCESS_FIELD, array(
|
||||
if ($display = \Drupal::entityTypeManager()->getStorage('entity_form_display')->load($entity_type . '.' . $bundle . '.default')) {
|
||||
$display->setComponent(DOMAIN_ACCESS_FIELD, [
|
||||
'type' => 'options_buttons',
|
||||
'weight' => 40,
|
||||
))
|
||||
->setComponent(DOMAIN_ACCESS_ALL_FIELD, array(
|
||||
])->setComponent(DOMAIN_ACCESS_ALL_FIELD, [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => array('display_label' => 1),
|
||||
'settings' => ['display_label' => 1],
|
||||
'weight' => 41,
|
||||
))
|
||||
->save();
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
\Drupal::logger('domain_access')->notice('Field installation failed.');
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ namespace Drupal\domain_access\Plugin\views\argument;
|
||||
use Drupal\views\Plugin\views\argument\StringArgument;
|
||||
|
||||
/**
|
||||
* Field handler to present the link an entity on a domain.
|
||||
* Argument handler to find nodes by domain assignment.
|
||||
*
|
||||
* @ViewsArgument("domain_access_argument")
|
||||
*/
|
||||
|
||||
+3
-3
@@ -11,8 +11,8 @@ dependencies:
|
||||
- domain_access
|
||||
- taxonomy
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
+3
-1
@@ -151,10 +151,12 @@ class DomainAccessFieldTest extends DomainTestBase {
|
||||
// Create a new content type and test that the fields are created.
|
||||
// Create a content type programmatically.
|
||||
$type = $this->drupalCreateContentType();
|
||||
|
||||
$type_exists = (bool) NodeType::load($type->id());
|
||||
$this->assertTrue($type_exists, 'The new content type has been created in the database.');
|
||||
|
||||
// The test is not passing to domain_access_node_type_insert() properly.
|
||||
domain_access_confirm_fields('node', $type->id());
|
||||
|
||||
// Visit the article creation page.
|
||||
$this->drupalGet('node/add/' . $type->id());
|
||||
$this->assertResponse(200, $type->id() . ' creation found.');
|
||||
|
||||
@@ -7,8 +7,8 @@ package: Domain
|
||||
dependencies:
|
||||
- domain
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
@@ -7,8 +7,8 @@ package: Domain
|
||||
dependencies:
|
||||
- domain
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
@@ -7,8 +7,8 @@ package: Domain
|
||||
dependencies:
|
||||
- domain
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
+3
-3
@@ -10,8 +10,8 @@ dependencies:
|
||||
- domain
|
||||
- domain_config
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
+3
-3
@@ -10,8 +10,8 @@ dependencies:
|
||||
- domain
|
||||
- domain_config
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
+10
-13
@@ -326,17 +326,17 @@ display:
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: 1
|
||||
link: true
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: 1
|
||||
delta_limit: '0'
|
||||
delta_offset: '0'
|
||||
delta_reversed: 0
|
||||
delta_first_last: 0
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: 0
|
||||
field_api_classes: false
|
||||
plugin_id: domain_access_field
|
||||
field_domain_all_affiliates:
|
||||
id: field_domain_all_affiliates
|
||||
@@ -770,7 +770,7 @@ display:
|
||||
operation: view
|
||||
multiple: 0
|
||||
access: false
|
||||
bundles: null
|
||||
bundles: { }
|
||||
glossary: false
|
||||
limit: 0
|
||||
case: none
|
||||
@@ -1213,10 +1213,7 @@ display:
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: '='
|
||||
value:
|
||||
min: ''
|
||||
max: ''
|
||||
value: '1'
|
||||
value: '1'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
@@ -1269,7 +1266,7 @@ display:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: 0
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
|
||||
+9
-9
@@ -402,17 +402,17 @@ display:
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: 0
|
||||
link: false
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: 1
|
||||
delta_limit: '0'
|
||||
delta_offset: '0'
|
||||
delta_reversed: 0
|
||||
delta_first_last: 0
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: 0
|
||||
field_api_classes: false
|
||||
plugin_id: domain_access_field
|
||||
field_domain_all_affiliates:
|
||||
id: field_domain_all_affiliates
|
||||
@@ -1080,7 +1080,7 @@ display:
|
||||
operation: view
|
||||
multiple: 0
|
||||
access: false
|
||||
bundles: null
|
||||
bundles: { }
|
||||
glossary: false
|
||||
limit: 0
|
||||
case: none
|
||||
@@ -1381,7 +1381,7 @@ display:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: 0
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Schema for the domain content plugins.
|
||||
|
||||
views.access.domain_content_admin:
|
||||
type: mapping
|
||||
label: 'Domain Content: View domain-specific editors'
|
||||
views.access.domain_content_editor:
|
||||
type: mapping
|
||||
label: 'Domain Content: View domain-specific content'
|
||||
@@ -11,8 +11,8 @@ dependencies:
|
||||
- user
|
||||
- views
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ class DomainContentController extends ControllerBase {
|
||||
];
|
||||
if ($account->hasPermission($options['all_permission'])) {
|
||||
$build['#rows'][] = [
|
||||
Link::fromTextAndUrl($this->t('All affiliates'), Url::fromUri('internal:/admin/content/' . $options['path'] . '/' . '/all_affiliates')),
|
||||
Link::fromTextAndUrl($this->t('All affiliates'), Url::fromUri('internal:/admin/content/' . $options['path'] . '/all_affiliates')),
|
||||
$this->getCount($options['type']),
|
||||
];
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
views.field.domain_source:
|
||||
type: views_field
|
||||
label: 'Domain source'
|
||||
views.filter.domain_source:
|
||||
type: views.filter.in_operator
|
||||
label: 'Domain source'
|
||||
views.filter_value.domain_source:
|
||||
type: views.filter_value.in_operator
|
||||
label: 'Domain source'
|
||||
@@ -8,8 +8,8 @@ dependencies:
|
||||
- domain
|
||||
- node
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-12-19
|
||||
version: '8.x-1.0-alpha11'
|
||||
# Information added by Drupal.org packaging script on 2018-03-08
|
||||
version: '8.x-1.0-alpha13'
|
||||
core: '8.x'
|
||||
project: 'domain'
|
||||
datestamp: 1513718589
|
||||
datestamp: 1520519900
|
||||
|
||||
@@ -54,14 +54,12 @@ function domain_source_confirm_fields($entity_type, $bundle) {
|
||||
}
|
||||
|
||||
// Tell the form system how to behave. Default to radio buttons.
|
||||
// @TODO: This function is deprecated, but using the OO syntax is causing
|
||||
// test fails.
|
||||
entity_get_form_display($entity_type, $bundle, 'default')
|
||||
->setComponent(DOMAIN_SOURCE_FIELD, array(
|
||||
if ($display = \Drupal::entityTypeManager()->getStorage('entity_form_display')->load($entity_type . '.' . $bundle . '.default')) {
|
||||
$display->setComponent(DOMAIN_SOURCE_FIELD, [
|
||||
'type' => 'options_select',
|
||||
'weight' => 42,
|
||||
))
|
||||
->save();
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,12 +128,20 @@ function domain_source_form_alter(&$form, &$form_state, $form_id) {
|
||||
function domain_source_form_validate($element, \Drupal\Core\Form\FormStateInterface $form_state) {
|
||||
$values = $form_state->getValues();
|
||||
// This is only run if Domain Access is present.
|
||||
$access_values = $values[DOMAIN_ACCESS_FIELD];
|
||||
$source_value = current($values[DOMAIN_SOURCE_FIELD]);
|
||||
if (isset($values[DOMAIN_SOURCE_FIELD]) && is_array($values[DOMAIN_SOURCE_FIELD]) && isset($values[DOMAIN_ACCESS_FIELD])) {
|
||||
$access_values = $values[DOMAIN_ACCESS_FIELD];
|
||||
$source_value = current($values[DOMAIN_SOURCE_FIELD]);
|
||||
}
|
||||
// If no value is selected, that's acceptable. Else run through a check.
|
||||
// Note that the _none selection returns as [FALSE].
|
||||
$source_set = empty($source_value);
|
||||
foreach ($access_values as $value) {
|
||||
if ($value == $source_value) {
|
||||
// Core is inconsistent depending on the field order.
|
||||
// See https://www.drupal.org/project/domain/issues/2945771#comment-12493199
|
||||
if (is_array($value) && $value == $source_value) {
|
||||
$source_set = TRUE;
|
||||
}
|
||||
elseif (is_string($value) && !empty($source_value['target_id']) && $value == $source_value['target_id']) {
|
||||
$source_set = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
+32
@@ -76,6 +76,10 @@ class DomainSourceElementTest extends DomainTestBase {
|
||||
$this->pressButton('edit-submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Check the URL.
|
||||
$url = $this->geturl();
|
||||
$this->assert(strpos($url, 'node/1/edit') === FALSE, 'Form submitted.');
|
||||
|
||||
// Edit the node.
|
||||
$this->drupalGet('node/1/edit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
@@ -90,5 +94,33 @@ class DomainSourceElementTest extends DomainTestBase {
|
||||
// Check the URL.
|
||||
$url = $this->geturl();
|
||||
$this->assert(strpos($url, 'node/1/edit') > 0, 'Form not submitted.');
|
||||
|
||||
// Set the field properly and save again.
|
||||
$this->selectFieldOption($locator, 'one_example_com');
|
||||
|
||||
// Save the form.
|
||||
$this->pressButton('edit-submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Check the URL.
|
||||
$url = $this->geturl();
|
||||
$this->assert(strpos($url, 'node/1/edit') === FALSE, 'Form submitted.');
|
||||
|
||||
// Save with no source.
|
||||
|
||||
// Edit the node.
|
||||
$this->drupalGet('node/1/edit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Set the domain source field to an unselected domain.
|
||||
$this->selectFieldOption($locator, '_none');
|
||||
|
||||
// Save the form.
|
||||
$this->pressButton('edit-submit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Check the URL.
|
||||
$url = $this->geturl();
|
||||
$this->assert(strpos($url, 'node/1/edit') === FALSE, 'Form submitted.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"name": "drupal/features",
|
||||
"description": "Enables administrators to package configuration into modules.",
|
||||
"type": "drupal-module",
|
||||
"description": "Enables administrators to package configuration into modules",
|
||||
"license": "GPL-2.0+",
|
||||
"minimum-stability": "dev",
|
||||
"require": { }
|
||||
"name": "drupal/features",
|
||||
"require": {
|
||||
"drupal/config_update": "^1.4"
|
||||
},
|
||||
"type": "drupal-module"
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ features.bundle.*:
|
||||
type: boolean
|
||||
label: "Is install profile"
|
||||
|
||||
features.assignment.*:
|
||||
features.assignment.settings:
|
||||
type: mapping
|
||||
label: "Assignment settings"
|
||||
mapping:
|
||||
@@ -49,16 +49,13 @@ features.assignment.*:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
|
||||
features.assignment.*:
|
||||
type: features.assignment.settings
|
||||
|
||||
features.assignment.base:
|
||||
type: mapping
|
||||
type: features.assignment.settings
|
||||
label: "Base type"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
@@ -75,15 +72,9 @@ features.assignment.base:
|
||||
type: string
|
||||
|
||||
features.assignment.core:
|
||||
type: mapping
|
||||
type: features.assignment.settings
|
||||
label: "Core type"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
@@ -95,15 +86,9 @@ features.assignment.core:
|
||||
type: string
|
||||
|
||||
features.assignment.exclude:
|
||||
type: mapping
|
||||
type: features.assignment.settings
|
||||
label: "Exclude"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
@@ -134,15 +119,9 @@ features.assignment.exclude:
|
||||
label: "Don't exclude ANY configuration by namespace"
|
||||
|
||||
features.assignment.optional:
|
||||
type: mapping
|
||||
type: features.assignment.settings
|
||||
label: "Optional"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
@@ -154,15 +133,9 @@ features.assignment.optional:
|
||||
type: string
|
||||
|
||||
features.assignment.profile:
|
||||
type: mapping
|
||||
type: features.assignment.settings
|
||||
label: "Profile"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
curated:
|
||||
type: boolean
|
||||
label: "Add commonly-needed configuration"
|
||||
@@ -187,15 +160,9 @@ features.assignment.profile:
|
||||
type: string
|
||||
|
||||
features.assignment.site:
|
||||
type: mapping
|
||||
type: features.assignment.settings
|
||||
label: "Site"
|
||||
mapping:
|
||||
enabled:
|
||||
type: boolean
|
||||
label: "Enabled"
|
||||
weight:
|
||||
type: integer
|
||||
label: "Weight"
|
||||
types:
|
||||
type: mapping
|
||||
label: "Types"
|
||||
@@ -205,3 +172,17 @@ features.assignment.site:
|
||||
label: "Configuration Types"
|
||||
sequence:
|
||||
type: string
|
||||
|
||||
features.assignment.alter:
|
||||
type: features.assignment.settings
|
||||
label: "Alter"
|
||||
mapping:
|
||||
core:
|
||||
type: boolean
|
||||
label: "Core"
|
||||
uuid:
|
||||
type: boolean
|
||||
label: "UUID"
|
||||
user_permissions:
|
||||
type: boolean
|
||||
label: "User permissions"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
features.commands:
|
||||
class: \Drupal\features\Commands\FeaturesCommands
|
||||
arguments:
|
||||
- '@features_assigner'
|
||||
- '@features.manager'
|
||||
- '@features_generator'
|
||||
- '@config_update.config_diff'
|
||||
- '@config.storage'
|
||||
tags:
|
||||
- { name: drush.command }
|
||||
@@ -7,8 +7,8 @@ dependencies:
|
||||
- config
|
||||
- config_update
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-03-07
|
||||
version: '8.x-3.5'
|
||||
# Information added by Drupal.org packaging script on 2018-02-27
|
||||
version: '8.x-3.7'
|
||||
core: '8.x'
|
||||
project: 'features'
|
||||
datestamp: 1488908587
|
||||
datestamp: 1519763291
|
||||
|
||||
@@ -7,8 +7,8 @@ configure: features.assignment
|
||||
dependencies:
|
||||
- features
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-03-07
|
||||
version: '8.x-3.5'
|
||||
# Information added by Drupal.org packaging script on 2018-02-27
|
||||
version: '8.x-3.7'
|
||||
core: '8.x'
|
||||
project: 'features'
|
||||
datestamp: 1488908587
|
||||
datestamp: 1519763291
|
||||
|
||||
+8
-9
@@ -641,16 +641,15 @@ class FeaturesEditForm extends FormBase {
|
||||
foreach (array('excluded', 'required') as $constraint) {
|
||||
$this->{$constraint} = array();
|
||||
$info = !empty($this->package->{'get' . $constraint}()) ? $this->package->{'get' . $constraint}() : array();
|
||||
if (($constraint == 'required') && (empty($info) || !is_array($info))) {
|
||||
// If required is True or empty array, add all config as required
|
||||
$info = $this->package->getConfigOrig();
|
||||
}
|
||||
foreach ($info as $item_name) {
|
||||
if (!isset($config[$item_name])) {
|
||||
continue;
|
||||
// $info may be boolean.
|
||||
if (is_array($info)) {
|
||||
foreach ($info as $item_name) {
|
||||
if (!isset($config[$item_name])) {
|
||||
continue;
|
||||
}
|
||||
$item = $config[$item_name];
|
||||
$this->{$constraint}[$item->getType()][$item->getShortName()] = $item->getLabel();
|
||||
}
|
||||
$item = $config[$item_name];
|
||||
$this->{$constraint}[$item->getType()][$item->getShortName()] = $item->getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -133,10 +133,7 @@ class FeaturesExportForm extends FormBase {
|
||||
// Add in un-packaged configuration items.
|
||||
$this->addUnpackaged($packages, $config_collection);
|
||||
|
||||
// Filter packages on bundle if selected.
|
||||
if (!$current_bundle->isDefault()) {
|
||||
$packages = $this->featuresManager->filterPackages($packages, $current_bundle->getMachineName(), TRUE);
|
||||
}
|
||||
$packages = $this->featuresManager->filterPackages($packages, $current_bundle->getMachineName());
|
||||
|
||||
// Pass the packages and bundle data for use in the form pre_render
|
||||
// callback.
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ class FeaturesBundleUITest extends WebTestBase {
|
||||
* The features bundle.
|
||||
*/
|
||||
protected function defaultBundle() {
|
||||
return $this->bundleStorage->load('default');
|
||||
return $this->bundleStorage->load(FeaturesBundleInterface::DEFAULT_BUNDLE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Exception;
|
||||
|
||||
class DomainException extends \DomainException {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\features\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException {
|
||||
|
||||
}
|
||||
@@ -364,8 +364,7 @@ class FeaturesAssigner implements FeaturesAssignerInterface {
|
||||
}
|
||||
}
|
||||
foreach ($new_bundles as $new_bundle) {
|
||||
$new_bundle = $this->createBundleFromDefault($new_bundle['machine_name'], $new_bundle['name'], $new_bundle['description'], $new_bundle['is_profile']);
|
||||
drupal_set_message($this->t('Features bundle @name automatically created.', ['@name' => $new_bundle->getName()]));
|
||||
$this->createBundleFromDefault($new_bundle['machine_name'], $new_bundle['name'], $new_bundle['description'], $new_bundle['is_profile']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class FeaturesConfigInstaller extends ConfigInstaller {
|
||||
list($major, $minor, ) = explode('.', \Drupal::VERSION);
|
||||
if ($major == 8 && $minor > 2) {
|
||||
// D8.3 added the %install_profile% argument.
|
||||
$install_profile = drupal_get_profile();
|
||||
$install_profile = \Drupal::installProfile();
|
||||
parent::__construct($config_factory, $active_storage, $typed_config, $config_manager, $event_dispatcher, $install_profile);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -35,7 +35,15 @@ class FeaturesInstallStorage extends ExtensionInstallStorage {
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
parent::__construct($config_storage, $directory, $collection, FALSE);
|
||||
list($major, $minor, ) = explode('.', \Drupal::VERSION);
|
||||
if ($major == 8 && $minor > 2) {
|
||||
// D8.3 added the %profile% argument.
|
||||
$profile = \Drupal::installProfile();
|
||||
parent::__construct($config_storage, $directory, $collection, FALSE, $profile);
|
||||
}
|
||||
else {
|
||||
parent::__construct($config_storage, $directory, $collection, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -326,10 +326,13 @@ class FeaturesManager implements FeaturesManagerInterface {
|
||||
// A package matches the namespace if:
|
||||
// - it's prefixed with the namespace, or
|
||||
// - it's assigned to a bundle named for the namespace, or
|
||||
// - we're looking only for exported packages and it's not exported.
|
||||
if (empty($namespace) || (strpos($package->getMachineName(), $namespace . '_') === 0) ||
|
||||
// - the namespace is the default bundle and it has an empty bundle, and
|
||||
// - we're not removing only exported packages, or
|
||||
// - we are removing only exported packages and it's not exported.
|
||||
if ((strpos($package->getMachineName(), $namespace . '_') === 0 ||
|
||||
($package->getBundle() && $package->getBundle() === $namespace) ||
|
||||
($only_exported && $package->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT)) {
|
||||
($namespace === FeaturesBundleInterface::DEFAULT_BUNDLE && empty($package->getBundle()))) &&
|
||||
(!$only_exported || ($package->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT))) {
|
||||
$result[$key] = $package;
|
||||
}
|
||||
}
|
||||
@@ -546,25 +549,30 @@ class FeaturesManager implements FeaturesManagerInterface {
|
||||
protected function getConfigDependency(ConfigurationItem $config, $module_list = array()) {
|
||||
$dependencies = [];
|
||||
$type = $config->getType();
|
||||
if ($type != FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG) {
|
||||
$provider = $this->entityTypeManager->getDefinition($type)->getProvider();
|
||||
// Ensure the provider is an installed module and not, for example, 'core'
|
||||
if (isset($module_list[$provider])) {
|
||||
$dependencies[] = $provider;
|
||||
|
||||
// For configuration in the InstallStorage::CONFIG_INSTALL_DIRECTORY
|
||||
// directory, set any dependencies of the configuration item as package
|
||||
// dependencies.
|
||||
// As its name implies, the core-provided
|
||||
// InstallStorage::CONFIG_OPTIONAL_DIRECTORY should not create
|
||||
// dependencies.
|
||||
if ($config->getSubdirectory() === InstallStorage::CONFIG_INSTALL_DIRECTORY) {
|
||||
if ($type === FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG) {
|
||||
$dependencies[] = strtok($config->getName(), '.');
|
||||
}
|
||||
else {
|
||||
$dependencies[] = $this->entityTypeManager->getDefinition($type)->getProvider();
|
||||
}
|
||||
|
||||
// For configuration in the InstallStorage::CONFIG_INSTALL_DIRECTORY
|
||||
// directory, set any module dependencies of the configuration item
|
||||
// as package dependencies.
|
||||
// As its name implies, the core-provided
|
||||
// InstallStorage::CONFIG_OPTIONAL_DIRECTORY should not create
|
||||
// dependencies.
|
||||
if ($config->getSubdirectory() === InstallStorage::CONFIG_INSTALL_DIRECTORY &&
|
||||
isset($config->getData()['dependencies']['module'])
|
||||
) {
|
||||
if (isset($config->getData()['dependencies']['module'])) {
|
||||
$dependencies = array_merge($dependencies, $config->getData()['dependencies']['module']);
|
||||
}
|
||||
|
||||
// Only return dependencies for installed modules and not, for example,
|
||||
// 'core'.
|
||||
$dependencies = array_intersect($dependencies, array_keys($module_list));
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
@@ -755,20 +763,28 @@ class FeaturesManager implements FeaturesManagerInterface {
|
||||
}
|
||||
|
||||
$config_collection = $this->getConfigCollection();
|
||||
$module_list = $this->moduleHandler->getModuleList();
|
||||
|
||||
/** @var \Drupal\features\Package[] $packages */
|
||||
foreach ($packages as $package) {
|
||||
foreach ($package->getConfig() as $item_name) {
|
||||
if (!empty($config_collection[$item_name]->getData()['dependencies']['config'])) {
|
||||
foreach ($config_collection[$item_name]->getData()['dependencies']['config'] as $dependency_name) {
|
||||
if (isset($config_collection[$dependency_name])) {
|
||||
if (isset($config_collection[$dependency_name]) &&
|
||||
// For configuration in the
|
||||
// InstallStorage::CONFIG_INSTALL_DIRECTORY directory, set any
|
||||
// package dependencies of the configuration item.
|
||||
// As its name implies, the core-provided
|
||||
// InstallStorage::CONFIG_OPTIONAL_DIRECTORY should not create
|
||||
// dependencies.
|
||||
($config_collection[$dependency_name]->getSubdirectory() === InstallStorage::CONFIG_INSTALL_DIRECTORY)) {
|
||||
// If the required item is assigned to one of the packages, add
|
||||
// a dependency on that package.
|
||||
$dependency_set = FALSE;
|
||||
if ($dependency_package = $config_collection[$dependency_name]->getPackage()) {
|
||||
$package_name = $bundle->getFullName($dependency_package);
|
||||
// Package shouldn't be dependent on itself.
|
||||
if ($package_name && array_key_exists($package_name, $packages) && $package_name != $package->getMachineName()) {
|
||||
if ($package_name && array_key_exists($package_name, $packages) && $package_name != $package->getMachineName() && isset($module_list[$package_name])) {
|
||||
$package->setDependencies($this->mergeUniqueItems($package->getDependencies(), [$package_name]));
|
||||
$dependency_set = TRUE;
|
||||
}
|
||||
@@ -778,7 +794,7 @@ class FeaturesManager implements FeaturesManagerInterface {
|
||||
if (!$dependency_set && $extension_name = $config_collection[$dependency_name]->getProvider()) {
|
||||
// No extension should depend on the install profile.
|
||||
$package_name = $bundle->getFullName($package->getMachineName());
|
||||
if ($extension_name != $package_name && $extension_name != $this->drupalGetProfile()) {
|
||||
if ($extension_name != $package_name && $extension_name != $this->drupalGetProfile() && isset($module_list[$extension_name])) {
|
||||
$package->setDependencies($this->mergeUniqueItems($package->getDependencies(), [$extension_name]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ class Package {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @return string[]|bool
|
||||
*/
|
||||
public function getRequired() {
|
||||
return $this->required;
|
||||
@@ -245,12 +245,20 @@ class Package {
|
||||
* @return bool
|
||||
*/
|
||||
public function getRequiredAll() {
|
||||
// Mark all as required if the package is not yet exported.
|
||||
if ($this->getStatus() === FeaturesManagerInterface::STATUS_NO_EXPORT) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Mark all as required if required is TRUE.
|
||||
if (is_bool($this->required)) {
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
// Mark all as required if required contains all the exported config.
|
||||
$config_orig = $this->getConfigOrig();
|
||||
$info = is_array($this->required) ? $this->required : array();
|
||||
$diff = array_diff($config_orig, $info);
|
||||
// Mark all as required if required:true, or required is empty, or
|
||||
// if required contains all the exported config
|
||||
return empty($diff) || empty($info);
|
||||
$diff = array_diff($config_orig, $this->required);
|
||||
return empty($diff);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ class FeaturesAssignmentPackages extends FeaturesAssignmentMethodBase {
|
||||
$short_name = $package->getMachineName();
|
||||
|
||||
// Copy over package excluded settings, if any.
|
||||
if (!$package->getExcluded()) {
|
||||
if ($package->getExcluded()) {
|
||||
$config_collection = $this->featuresManager->getConfigCollection();
|
||||
foreach ($package->getExcluded() as $config_name) {
|
||||
if (isset($config_collection[$config_name])) {
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ package: Test
|
||||
dependencies:
|
||||
- features
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-03-07
|
||||
version: '8.x-3.5'
|
||||
# Information added by Drupal.org packaging script on 2018-02-27
|
||||
version: '8.x-3.7'
|
||||
core: '8.x'
|
||||
project: 'features'
|
||||
datestamp: 1488908587
|
||||
datestamp: 1519763291
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
bundle: test_mybundle
|
||||
excluded:
|
||||
- system.theme
|
||||
required:
|
||||
- core.date_format.long
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ package: Test
|
||||
dependencies:
|
||||
- features
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-03-07
|
||||
version: '8.x-3.5'
|
||||
# Information added by Drupal.org packaging script on 2018-02-27
|
||||
version: '8.x-3.7'
|
||||
core: '8.x'
|
||||
project: 'features'
|
||||
datestamp: 1488908587
|
||||
datestamp: 1519763291
|
||||
|
||||
@@ -700,6 +700,19 @@ class FeaturesAssignTest extends KernelTestBase {
|
||||
public function testAssignPackages() {
|
||||
$method_id = 'packages';
|
||||
|
||||
// A configuration item that will be excluded.
|
||||
$this->addConfigurationItem('system.theme', [], [
|
||||
'type' => FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG,
|
||||
]);
|
||||
// A configuration item that will be required.
|
||||
$this->addConfigurationItem('core.date_format.long', [], [
|
||||
'type' => 'date_format',
|
||||
]);
|
||||
// A configuration item that will be neither excluded nor required.
|
||||
$this->addConfigurationItem('system.theme', [], [
|
||||
'type' => FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG,
|
||||
]);
|
||||
|
||||
// Enable the method.
|
||||
$this->enableAssignmentMethod($method_id);
|
||||
|
||||
@@ -708,6 +721,14 @@ class FeaturesAssignTest extends KernelTestBase {
|
||||
$packages = $this->featuresManager->getPackages();
|
||||
|
||||
$this->assertNotEmpty($packages[self::TEST_INSTALLED_PACKAGE], 'Expected package not created.');
|
||||
|
||||
$config = $this->featuresManager->getConfigCollection();
|
||||
|
||||
$this->assertTrue(in_array(self::TEST_INSTALLED_PACKAGE, $config['system.theme']->getPackageExcluded()), 'Configuration item not excluded from package.');
|
||||
$this->assertFalse(in_array(self::TEST_INSTALLED_PACKAGE, $config['system.site']->getPackageExcluded()), 'Configuration item excluded from package.');
|
||||
|
||||
$this->assertEquals(self::TEST_INSTALLED_PACKAGE, $config['core.date_format.long']->getPackage(), 'Required item not assigned to package.');
|
||||
$this->assertNotEquals(self::TEST_INSTALLED_PACKAGE, $config['system.site']->getPackage(), 'Unrequired item assigned to package.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -730,7 +751,7 @@ class FeaturesAssignTest extends KernelTestBase {
|
||||
'type' => 'image_style',
|
||||
]);
|
||||
$this->addConfigurationItem('system.cron', [], [
|
||||
'type' => 'simple',
|
||||
'type' => FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG,
|
||||
]);
|
||||
$this->bundle = $this->assigner->createBundleFromDefault('myprofile');
|
||||
$this->bundle->setProfileName('myprofile');
|
||||
|
||||
@@ -105,10 +105,23 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
$this->configManager = $this->getMock(ConfigManagerInterface::class);
|
||||
$this->moduleHandler = $this->getMock(ModuleHandlerInterface::class);
|
||||
// getModuleList should return an array of extension objects.
|
||||
// but we just need ::getConfigDependency isset($module_list[$provider]).
|
||||
// but we just need isset($module_list[$provider]) for
|
||||
// ::getConfigDependency() and ::assignInterPackageDependencies().
|
||||
$this->moduleHandler->expects($this->any())
|
||||
->method('getModuleList')
|
||||
->willReturn(['my_module' => true]);
|
||||
->willReturn([
|
||||
'my_module' => true,
|
||||
'example' => true,
|
||||
'example3' => true,
|
||||
'my_feature' => true,
|
||||
'my_other_feature' => true,
|
||||
'package' => true,
|
||||
'package2' => true,
|
||||
'package3' => true,
|
||||
'giraffe_package' => true,
|
||||
'giraffe_package2' => true,
|
||||
'giraffe_package3' => true,
|
||||
]);
|
||||
$this->configReverter = $this->getMock(ConfigRevertInterface::class);
|
||||
$this->configReverter->expects($this->any())
|
||||
->method('import')
|
||||
@@ -188,6 +201,55 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
$this->assertEquals($package, $this->featuresManager->getPackage('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::filterPackages
|
||||
*/
|
||||
public function testGetPackages() {
|
||||
$packages = [
|
||||
'package' => new Package('package', [
|
||||
'bundle' => '',
|
||||
'status' => FeaturesManagerInterface::STATUS_NO_EXPORT,
|
||||
]),
|
||||
'package2' => new Package('package2', [
|
||||
'bundle' => '',
|
||||
'status' => FeaturesManagerInterface::STATUS_UNINSTALLED,
|
||||
]),
|
||||
'package3' => new Package('package3', [
|
||||
'bundle' => 'my_bundle',
|
||||
'status' => FeaturesManagerInterface::STATUS_NO_EXPORT,
|
||||
]),
|
||||
'package4' => new Package('package4', [
|
||||
'bundle' => 'my_bundle',
|
||||
'status' => FeaturesManagerInterface::STATUS_UNINSTALLED,
|
||||
]),
|
||||
];
|
||||
|
||||
// Filter for the default bundle.
|
||||
$filtered_packages = $this->featuresManager->filterPackages($packages, FeaturesBundleInterface::DEFAULT_BUNDLE);
|
||||
$this->assertEquals(['package', 'package2'], array_keys($filtered_packages));
|
||||
|
||||
// Filter for a custom bundle.
|
||||
$filtered_packages = $this->featuresManager->filterPackages($packages, 'my_bundle');
|
||||
$this->assertEquals(['package3', 'package4'], array_keys($filtered_packages));
|
||||
|
||||
// Filter for a non-matching bundle.
|
||||
$filtered_packages = $this->featuresManager->filterPackages($packages, 'some_bundle');
|
||||
$this->assertEquals([], array_keys($filtered_packages));
|
||||
|
||||
// Filter for the default bundle removing only exported.
|
||||
$filtered_packages = $this->featuresManager->filterPackages($packages, FeaturesBundleInterface::DEFAULT_BUNDLE, TRUE);
|
||||
$this->assertEquals(['package'], array_keys($filtered_packages));
|
||||
|
||||
// Filter for a custom bundle removing only exported.
|
||||
$filtered_packages = $this->featuresManager->filterPackages($packages, 'my_bundle', TRUE);
|
||||
$this->assertEquals(['package3'], array_keys($filtered_packages));
|
||||
|
||||
// Filter for a non-matching bundle removing only exported.
|
||||
$filtered_packages = $this->featuresManager->filterPackages($packages, 'some_bundle', TRUE);
|
||||
$this->assertEquals([], array_keys($filtered_packages));
|
||||
}
|
||||
|
||||
|
||||
protected function getAssignInterPackageDependenciesConfigCollection() {
|
||||
$config_collection = [];
|
||||
$config_collection['example.config'] = (new ConfigurationItem('example.config', [
|
||||
@@ -196,23 +258,46 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
'example.config2',
|
||||
'example.config3',
|
||||
'example.config4',
|
||||
'example.config5',
|
||||
'example.config6',
|
||||
'example.config7',
|
||||
],
|
||||
],
|
||||
]))->setPackage('package');
|
||||
|
||||
]))
|
||||
->setSubdirectory(InstallStorage::CONFIG_INSTALL_DIRECTORY)
|
||||
->setPackage('package');
|
||||
$config_collection['example.config2'] = (new ConfigurationItem('example.config2', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setSubdirectory(InstallStorage::CONFIG_INSTALL_DIRECTORY)
|
||||
->setPackage('package2')
|
||||
->setProvider('my_feature');
|
||||
$config_collection['example.config3'] = (new ConfigurationItem('example.config3', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setSubdirectory(InstallStorage::CONFIG_INSTALL_DIRECTORY)
|
||||
->setProvider('my_other_feature');
|
||||
$config_collection['example.config4'] = (new ConfigurationItem('example.config3', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setSubdirectory(InstallStorage::CONFIG_INSTALL_DIRECTORY)
|
||||
->setProvider(static::PROFILE_NAME);
|
||||
$config_collection['example.config5'] = (new ConfigurationItem('example.config5', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setSubdirectory(InstallStorage::CONFIG_OPTIONAL_DIRECTORY)
|
||||
->setPackage('package3');
|
||||
$config_collection['example.config6'] = (new ConfigurationItem('example.config6', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setSubdirectory(InstallStorage::CONFIG_INSTALL_DIRECTORY)
|
||||
->setProvider('my_uninstalled_feature');
|
||||
$config_collection['example.config7'] = (new ConfigurationItem('example.config7', [
|
||||
'dependencies' => [],
|
||||
]))
|
||||
->setSubdirectory(InstallStorage::CONFIG_INSTALL_DIRECTORY)
|
||||
->setProvider('package4');
|
||||
|
||||
return $config_collection;
|
||||
}
|
||||
|
||||
@@ -225,8 +310,10 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
// Provide a bundle without any prefix.
|
||||
$bundle->getFullName('package')->willReturn('package');
|
||||
$bundle->getFullName('package2')->willReturn('package2');
|
||||
$bundle->getFullName('package3')->willReturn('package3');
|
||||
$bundle->getFullName('package4')->willReturn('package4');
|
||||
$bundle->isDefault()->willReturn(TRUE);
|
||||
$assigner->getBundle('')->willReturn($bundle->reveal());
|
||||
$assigner->getBundle()->willReturn($bundle->reveal());
|
||||
// Use the wrapper because we need ::drupalGetProfile().
|
||||
$features_manager = new TestFeaturesManager($this->root, $this->entityTypeManager, $this->configFactory, $this->configStorage, $this->configManager, $this->moduleHandler, $this->configReverter);
|
||||
$features_manager->setAssigner($assigner->reveal());
|
||||
@@ -244,6 +331,16 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
'dependencies' => [],
|
||||
'bundle' => '',
|
||||
]),
|
||||
'package3' => new Package('package3', [
|
||||
'config' => ['example.config5'],
|
||||
'dependencies' => [],
|
||||
'bundle' => '',
|
||||
]),
|
||||
'package4' => new Package('package4', [
|
||||
'config' => ['example.config7'],
|
||||
'dependencies' => [],
|
||||
'bundle' => '',
|
||||
]),
|
||||
];
|
||||
|
||||
$features_manager->setPackages($packages);
|
||||
@@ -257,6 +354,13 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
// my_package.
|
||||
// Because package assignments take precedence over providing_feature ones,
|
||||
// package2 should have been assigned rather than my_feature.
|
||||
// Because it is assigned to the InstallStorage::CONFIG_OPTIONAL_DIRECTORY
|
||||
// subdirectory, example.config5 does not create a dependency on its
|
||||
// providing feature, package3.
|
||||
// Because it's provided by an uninstalled module, example.config6 doesn't
|
||||
// create a dependency on my_uninstalled_feature.
|
||||
// Because it's provided by an uninstalled module, example.config7 doesn't
|
||||
// create a dependency on package4.
|
||||
$this->assertEquals(['my_other_feature', 'package2'], $packages['package']->getDependencies());
|
||||
$this->assertEquals([], $packages['package2']->getDependencies());
|
||||
}
|
||||
@@ -270,6 +374,8 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
// Provide a bundle without any prefix.
|
||||
$bundle->getFullName('package')->willReturn('giraffe_package');
|
||||
$bundle->getFullName('package2')->willReturn('giraffe_package2');
|
||||
$bundle->getFullName('package3')->willReturn('giraffe_package3');
|
||||
$bundle->getFullName('package4')->willReturn('giraffe_package4');
|
||||
$bundle->getFullName('giraffe_package')->willReturn('giraffe_package');
|
||||
$bundle->getFullName('giraffe_package2')->willReturn('giraffe_package2');
|
||||
$bundle->isDefault()->willReturn(FALSE);
|
||||
@@ -291,6 +397,16 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
]),
|
||||
'package3' => new Package('package3', [
|
||||
'config' => ['example.config5'],
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
]),
|
||||
'package4' => new Package('package4', [
|
||||
'config' => ['example.config7'],
|
||||
'dependencies' => [],
|
||||
'bundle' => 'giraffe',
|
||||
]),
|
||||
];
|
||||
|
||||
$features_manager->setPackages($packages);
|
||||
@@ -304,6 +420,13 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
// my_package.
|
||||
// Because package assignments take precedence over providing_feature ones,
|
||||
// package2 should have been assigned rather than my_feature.
|
||||
// Because it is assigned to the InstallStorage::CONFIG_OPTIONAL_DIRECTORY
|
||||
// subdirectory, example.config5 does not create a dependency on its
|
||||
// providing feature, package3.
|
||||
// Because it's provided by an uninstalled module, example.config6 doesn't
|
||||
// create a dependency on my_uninstalled_feature.
|
||||
// Because it's provided by an uninstalled module, example.config7 doesn't
|
||||
// create a dependency on giraffe_package4.
|
||||
$expected = ['giraffe_package2', 'my_other_feature'];
|
||||
$this->assertEquals($expected, $packages['giraffe_package']->getDependencies());
|
||||
}
|
||||
@@ -417,6 +540,7 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
$assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$bundle = $this->prophesize(FeaturesBundleInterface::class);
|
||||
$bundle->isProfilePackage('test_package')->willReturn(FALSE);
|
||||
$bundle->isProfilePackage('test_package2')->willReturn(FALSE);
|
||||
$assigner->getBundle(NULL)->willReturn($bundle->reveal());
|
||||
$this->featuresManager->setAssigner($assigner->reveal());
|
||||
|
||||
@@ -424,21 +548,43 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
'test_config' => new ConfigurationItem('test_config', []),
|
||||
'test_config2' => new ConfigurationItem('test_config2', [
|
||||
'dependencies' => [
|
||||
'module' => ['example'],
|
||||
'module' => ['example', 'example2'],
|
||||
]
|
||||
], [
|
||||
'subdirectory' => InstallStorage::CONFIG_INSTALL_DIRECTORY,
|
||||
]),
|
||||
'example3.settings' => new ConfigurationItem('example3.settings', [], [
|
||||
'type' => FeaturesManagerInterface::SYSTEM_SIMPLE_CONFIG,
|
||||
'subdirectory' => InstallStorage::CONFIG_INSTALL_DIRECTORY,
|
||||
]),
|
||||
'test_config3' => new ConfigurationItem('test_config3', [
|
||||
'dependencies' => [
|
||||
'module' => ['example2'],
|
||||
]
|
||||
], [
|
||||
'subdirectory' => InstallStorage::CONFIG_OPTIONAL_DIRECTORY,
|
||||
]),
|
||||
];
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
|
||||
$package = new Package('test_package');
|
||||
$this->featuresManager->setPackage($package);
|
||||
|
||||
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2']);
|
||||
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2', 'example3.settings']);
|
||||
|
||||
$this->assertEquals(['test_config', 'test_config2'], $this->featuresManager->getPackage('test_package')->getConfig());
|
||||
$this->assertEquals(['example', 'my_module'], $this->featuresManager->getPackage('test_package')->getDependencies());
|
||||
$this->assertEquals(['test_config', 'test_config2', 'example3.settings'], $this->featuresManager->getPackage('test_package')->getConfig());
|
||||
// 'example2' is not returned by ::getModuleList() and so isn't a
|
||||
// dependency.
|
||||
$this->assertEquals(['example', 'example3', 'my_module'], $this->featuresManager->getPackage('test_package')->getDependencies());
|
||||
|
||||
// Test optional config, which doesn't create module dependencies.
|
||||
$package = new Package('test_package2');
|
||||
$this->featuresManager->setPackage($package);
|
||||
|
||||
$this->featuresManager->assignConfigPackage('test_package2', ['test_config3']);
|
||||
|
||||
$this->assertEquals(['test_config3'], $this->featuresManager->getPackage('test_package2')->getConfig());
|
||||
$this->assertEquals([], $this->featuresManager->getPackage('test_package2')->getDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,6 +597,32 @@ class FeaturesManagerTest extends UnitTestCase {
|
||||
];
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
|
||||
$feature_assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$feature_assigner->getBundle(NULL)->willReturn(new FeaturesBundle(['machine_name' => FeaturesBundleInterface::DEFAULT_BUNDLE], 'features_bundle'));
|
||||
$this->featuresManager->setAssigner($feature_assigner->reveal());
|
||||
|
||||
$package = new Package('test_package');
|
||||
$original_package = clone $package;
|
||||
|
||||
$this->featuresManager->setPackage($package);
|
||||
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2']);
|
||||
$this->assertEquals(['test_config'], $this->featuresManager->getPackage('test_package')->getConfig(), 'just assign new packages');
|
||||
|
||||
$this->featuresManager->setPackage($original_package);
|
||||
$this->featuresManager->assignConfigPackage('test_package', ['test_config', 'test_config2'], TRUE);
|
||||
$this->assertEquals(['test_config', 'test_config2'], $this->featuresManager->getPackage('test_package')->getConfig(), 'just assign new packages');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::assignConfigPackage
|
||||
*/
|
||||
public function testAssignConfigPackageWithPackageExcludedConfig() {
|
||||
$config_collection = [
|
||||
'test_config' => new ConfigurationItem('test_config', []),
|
||||
'test_config2' => new ConfigurationItem('test_config2', [], ['packageExcluded' => ['test_package']]),
|
||||
];
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
|
||||
$feature_assigner = $this->prophesize(FeaturesAssignerInterface::class);
|
||||
$feature_assigner->getBundle(NULL)->willReturn(new FeaturesBundle(['machine_name' => 'default'], 'features_bundle'));
|
||||
$this->featuresManager->setAssigner($feature_assigner->reveal());
|
||||
@@ -687,6 +859,7 @@ EOT
|
||||
|
||||
/**
|
||||
* @covers ::prepareFiles
|
||||
* @covers ::addInfoFile
|
||||
*/
|
||||
public function testPrepareFiles() {
|
||||
$packages = [];
|
||||
@@ -695,12 +868,22 @@ EOT
|
||||
'name' => 'Test feature',
|
||||
]);
|
||||
|
||||
$packages['test_feature2'] = new Package('test_feature2', [
|
||||
'config' => ['test_config2'],
|
||||
'name' => 'Test feature 2',
|
||||
'type' => 'profile',
|
||||
'excluded' => ['my_config'],
|
||||
'required' => ['test_config2'],
|
||||
]);
|
||||
|
||||
$config_collection = [];
|
||||
$config_collection['test_config'] = new ConfigurationItem('test_config', ['foo' => 'bar']);
|
||||
$config_collection['test_config2'] = new ConfigurationItem('test_config2', ['foo' => 'bar']);
|
||||
|
||||
$this->featuresManager->setConfigCollection($config_collection);
|
||||
$this->featuresManager->prepareFiles($packages);
|
||||
|
||||
// Test test_feature package.
|
||||
$files = $packages['test_feature']->getFiles();
|
||||
$this->assertCount(3, $files);
|
||||
$this->assertEquals('test_feature.info.yml', $files['info']['filename']);
|
||||
@@ -718,6 +901,14 @@ EOT
|
||||
|
||||
$this->assertEquals('test_feature.features.yml', $files['features']['filename']);
|
||||
$this->assertEquals(Yaml::encode(TRUE), $files['features']['string']);
|
||||
|
||||
// Test test_feature2 package.
|
||||
$files = $packages['test_feature2']->getFiles();
|
||||
|
||||
$this->assertEquals(Yaml::encode([
|
||||
'excluded' => ['my_config'],
|
||||
'required' => ['test_config2'],
|
||||
]), $files['features']['string']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,7 @@ before_script:
|
||||
# Export database variable for kernel tests.
|
||||
- export SIMPLETEST_DB=mysql://root:@127.0.0.1/entity
|
||||
# Download Drupal 8 core.
|
||||
- travis_retry git clone --branch 8.3.x --depth 1 http://git.drupal.org/project/drupal.git
|
||||
- travis_retry git clone --branch 8.5.x --depth 1 http://git.drupal.org/project/drupal.git
|
||||
- cd drupal
|
||||
- composer self-update
|
||||
- composer install -n
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"homepage": "http://drupal.org/project/entity",
|
||||
"license": "GPL-2.0+",
|
||||
"require": {
|
||||
"drupal/core": "~8.3"
|
||||
"drupal/core": "~8.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ description: Provides expanded entity APIs, which will be moved to Drupal core o
|
||||
type: module
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- drupal:system (>=8.3.0)
|
||||
- drupal:system (>=8.5.0)
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-09-20
|
||||
version: '8.x-1.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2018-03-12
|
||||
version: '8.x-1.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'entity'
|
||||
datestamp: 1505895847
|
||||
datestamp: 1520873296
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
entity.entity_actions:
|
||||
deriver: Drupal\entity\Plugin\Derivative\EntityActionsDeriver
|
||||
@@ -1,4 +1,10 @@
|
||||
services:
|
||||
access_check.entity_delete_multiple:
|
||||
class: Drupal\entity\Access\EntityDeleteMultipleAccessCheck
|
||||
arguments: ['@entity_type.manager', '@tempstore.private', '@request_stack']
|
||||
tags:
|
||||
- { name: access_check, applies_to: _entity_delete_multiple_access }
|
||||
|
||||
access_checker.entity_revision:
|
||||
class: \Drupal\entity\Access\EntityRevisionRouteAccessChecker
|
||||
arguments: ['@entity_type.manager', '@current_route_match']
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Checks if the current user has delete access to the items of the tempstore.
|
||||
*/
|
||||
class EntityDeleteMultipleAccessCheck implements AccessInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The tempstore service.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* Request stack service.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityDeleteMultipleAccessCheck.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore service.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, RequestStack $request_stack) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
$this->requestStack = $request_stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has delete access for at least one item of the store.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* Run access checks for this account.
|
||||
* @param string $entity_type_id
|
||||
* Entity type ID.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* Allowed or forbidden, neutral if tempstore is empty.
|
||||
*/
|
||||
public function access(AccountInterface $account, $entity_type_id) {
|
||||
if (!$this->requestStack->getCurrentRequest()->getSession()) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
$selection = $this->tempStore->get($account->id() . ':' . $entity_type_id);
|
||||
if (empty($selection) || !is_array($selection)) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
|
||||
$entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($selection));
|
||||
foreach ($entities as $entity) {
|
||||
// As long as the user has access to delete one entity allow access to the
|
||||
// delete form. Access will be checked again in
|
||||
// Drupal\Core\Entity\Form\DeleteMultipleForm::submit() in case it has
|
||||
// changed in the meantime.
|
||||
if ($entity->access('delete', $account)) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,7 @@ trait RevisionControllerTrait {
|
||||
*
|
||||
* @return \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
public abstract function languageManager();
|
||||
abstract public function languageManager();
|
||||
|
||||
/**
|
||||
* Determines if the user has permission to revert revisions.
|
||||
@@ -55,7 +55,6 @@ trait RevisionControllerTrait {
|
||||
*
|
||||
* @return array
|
||||
* A link render array.
|
||||
*
|
||||
*/
|
||||
abstract protected function buildRevertRevisionLink(EntityInterface $entity_revision);
|
||||
|
||||
@@ -117,24 +116,17 @@ trait RevisionControllerTrait {
|
||||
$langcode = $this->languageManager()
|
||||
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
|
||||
->getId();
|
||||
$entity_storage = $this->entityTypeManager()
|
||||
->getStorage($entity->getEntityTypeId());
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $entity_storage */
|
||||
$entity_storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId());
|
||||
$revision_ids = $this->revisionIds($entity);
|
||||
$entity_revisions = $entity_storage->loadMultipleRevisions($revision_ids);
|
||||
|
||||
$header = [$this->t('Revision'), $this->t('Operations')];
|
||||
$rows = [];
|
||||
|
||||
$revision_ids = $this->revisionIds($entity);
|
||||
// @todo Expand the entity storage to load multiple revisions.
|
||||
$entity_revisions = array_combine($revision_ids, array_map(function($vid) use ($entity_storage) {
|
||||
return $entity_storage->loadRevision($vid);
|
||||
}, $revision_ids));
|
||||
|
||||
foreach ($entity_revisions as $revision) {
|
||||
$row = [];
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)
|
||||
->isRevisionTranslationAffected()
|
||||
) {
|
||||
if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
|
||||
$row[] = $this->getRevisionDescription($revision, $revision->isDefaultRevision());
|
||||
|
||||
if ($revision->isDefaultRevision()) {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
interface RevisionableEntityBundleInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Returns whether a new revision should be created by default.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if a new revision should be created by default.
|
||||
*/
|
||||
public function shouldCreateNewRevision();
|
||||
|
||||
}
|
||||
@@ -2,13 +2,7 @@
|
||||
|
||||
namespace Drupal\entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides generic entity permissions which are still cacheable.
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityViewBuilder as CoreEntityViewBuilder;
|
||||
|
||||
/**
|
||||
* Provides a entity view builder with contextual links support
|
||||
*/
|
||||
class EntityViewBuilder extends CoreEntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
if (($entity instanceof ContentEntityInterface && $entity->isDefaultRevision()) || !$entity->getEntityType()->isRevisionable()) {
|
||||
$build['#contextual_links'][$entity_type_id] = [
|
||||
'route_parameters' => [
|
||||
$entity_type_id => $entity->id()
|
||||
],
|
||||
];
|
||||
}
|
||||
else {
|
||||
$build['#contextual_links'][$entity_type_id . '_revision'] = [
|
||||
'route_parameters' => [
|
||||
$entity_type_id => $entity->id(),
|
||||
$entity_type_id . '_revision' => $entity->getRevisionId(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an entities deletion confirmation form.
|
||||
*/
|
||||
class DeleteMultiple extends ConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The tempstore.
|
||||
*
|
||||
* @var \Drupal\user\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The entity type id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId;
|
||||
|
||||
/**
|
||||
* The selection, in the entity_id => langcodes format.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selection = [];
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteMultiple object.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
*/
|
||||
public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('current_user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('user.private_tempstore')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'entity_delete_multiple_confirm';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.' . $this->entityTypeId . '.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type id.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
|
||||
$this->entityTypeId = $entity_type_id;
|
||||
$this->selection = $this->tempStore->get($this->currentUser->id());
|
||||
if (empty($this->entityTypeId) || empty($this->selection)) {
|
||||
return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
|
||||
}
|
||||
|
||||
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
$entities = $storage->loadMultiple(array_keys($this->selection));
|
||||
$items = [];
|
||||
foreach ($this->selection as $id => $langcodes) {
|
||||
foreach ($langcodes as $langcode) {
|
||||
$entity = $entities[$id]->getTranslation($langcode);
|
||||
$key = $id . ':' . $langcode;
|
||||
$default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
|
||||
|
||||
// If we have a translated entity we build a nested list of translations
|
||||
// that will be deleted.
|
||||
$languages = $entity->getTranslationLanguages();
|
||||
if (count($languages) > 1 && $entity->isDefaultTranslation()) {
|
||||
$names = [];
|
||||
foreach ($languages as $translation_langcode => $language) {
|
||||
$names[] = $language->getName();
|
||||
unset($items[$id . ':' . $translation_langcode]);
|
||||
}
|
||||
$items[$default_key] = [
|
||||
'label' => [
|
||||
'#markup' => $this->t('@label (Original translation) - <em>The following translations will be deleted:</em>', ['@label' => $entity->label()]),
|
||||
],
|
||||
'deleted_translations' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $names,
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif (!isset($items[$default_key])) {
|
||||
$items[$key] = $entity->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form['entities'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
];
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$total_count = 0;
|
||||
$delete_entities = [];
|
||||
$delete_translations = [];
|
||||
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
$entities = $storage->loadMultiple(array_keys($this->selection));
|
||||
|
||||
foreach ($this->selection as $id => $langcodes) {
|
||||
foreach ($langcodes as $langcode) {
|
||||
$entity = $entities[$id]->getTranslation($langcode);
|
||||
if ($entity->isDefaultTranslation()) {
|
||||
$delete_entities[$id] = $entity;
|
||||
unset($delete_translations[$id]);
|
||||
$total_count += count($entity->getTranslationLanguages());
|
||||
}
|
||||
elseif (!isset($delete_entities[$id])) {
|
||||
$delete_translations[$id][] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete_entities) {
|
||||
$storage->delete($delete_entities);
|
||||
$this->logger('content')->notice('Deleted @count @entity_type items.', [
|
||||
'@count' => count($delete_entities),
|
||||
'@entity_type' => $this->entityTypeId,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($delete_translations) {
|
||||
$count = 0;
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */
|
||||
foreach ($delete_translations as $id => $translations) {
|
||||
$entity = $entities[$id]->getUntranslated();
|
||||
foreach ($translations as $translation) {
|
||||
$entity->removeTranslation($translation->language()->getId());
|
||||
}
|
||||
$entity->save();
|
||||
$count += count($translations);
|
||||
}
|
||||
if ($count) {
|
||||
$total_count += $count;
|
||||
$this->logger('content')->notice('Deleted @count @entity_type translations.', [
|
||||
'@count' => $count,
|
||||
'@entity_type' => $this->entityTypeId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($total_count) {
|
||||
drupal_set_message($this->formatPlural($total_count, 'Deleted 1 item.', 'Deleted @count items.'));
|
||||
}
|
||||
$this->tempStore->delete($this->currentUser->id());
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\BaseFormIdInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an entities deletion confirmation form.
|
||||
*/
|
||||
class DeleteMultipleForm extends ConfirmFormBase implements BaseFormIdInterface {
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The tempstore.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The messenger service.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* The entity type ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId;
|
||||
|
||||
/**
|
||||
* The selection, in the entity_id => langcodes format.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selection = [];
|
||||
|
||||
/**
|
||||
* The entity type definition.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteMultiple object.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger service.
|
||||
*/
|
||||
public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, MessengerInterface $messenger) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('current_user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('messenger')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBaseFormId() {
|
||||
return 'entity_delete_multiple_confirm_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
// Get entity type ID from the route because ::buildForm has not yet been
|
||||
// called.
|
||||
$entity_type_id = $this->getRouteMatch()->getParameter('entity_type_id');
|
||||
return $entity_type_id . '_delete_multiple_confirm_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this @item?', 'Are you sure you want to delete these @items?', [
|
||||
'@item' => $this->entityType->getSingularLabel(),
|
||||
'@items' => $this->entityType->getPluralLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
if ($this->entityType->hasLinkTemplate('collection')) {
|
||||
return new Url('entity.' . $this->entityTypeId . '.collection');
|
||||
}
|
||||
else {
|
||||
return new Url('<front>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) {
|
||||
$this->entityTypeId = $entity_type_id;
|
||||
$this->entityType = $this->entityTypeManager->getDefinition($this->entityTypeId);
|
||||
$this->selection = $this->tempStore->get($this->currentUser->id() . ':' . $entity_type_id);
|
||||
if (empty($this->entityTypeId) || empty($this->selection)) {
|
||||
return new RedirectResponse($this->getCancelUrl()
|
||||
->setAbsolute()
|
||||
->toString());
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($this->selection));
|
||||
foreach ($this->selection as $id => $selected_langcodes) {
|
||||
$entity = $entities[$id];
|
||||
foreach ($selected_langcodes as $langcode) {
|
||||
$key = $id . ':' . $langcode;
|
||||
if ($entity instanceof TranslatableInterface) {
|
||||
$entity = $entity->getTranslation($langcode);
|
||||
$default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
|
||||
|
||||
// Build a nested list of translations that will be deleted if the
|
||||
// entity has multiple translations.
|
||||
$entity_languages = $entity->getTranslationLanguages();
|
||||
if (count($entity_languages) > 1 && $entity->isDefaultTranslation()) {
|
||||
$names = [];
|
||||
foreach ($entity_languages as $translation_langcode => $language) {
|
||||
$names[] = $language->getName();
|
||||
unset($items[$id . ':' . $translation_langcode]);
|
||||
}
|
||||
$items[$default_key] = [
|
||||
'label' => [
|
||||
'#markup' => $this->t('@label (Original translation) - <em>The following @entity_type translations will be deleted:</em>',
|
||||
[
|
||||
'@label' => $entity->label(),
|
||||
'@entity_type' => $this->entityType->getSingularLabel(),
|
||||
]),
|
||||
],
|
||||
'deleted_translations' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $names,
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif (!isset($items[$default_key])) {
|
||||
$items[$key] = $entity->label();
|
||||
}
|
||||
}
|
||||
elseif (!isset($items[$key])) {
|
||||
$items[$key] = $entity->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form['entities'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
];
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$total_count = 0;
|
||||
$delete_entities = [];
|
||||
$delete_translations = [];
|
||||
$inaccessible_entities = [];
|
||||
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
|
||||
|
||||
$entities = $storage->loadMultiple(array_keys($this->selection));
|
||||
foreach ($this->selection as $id => $selected_langcodes) {
|
||||
$entity = $entities[$id];
|
||||
if (!$entity->access('delete', $this->currentUser)) {
|
||||
$inaccessible_entities[] = $entity;
|
||||
continue;
|
||||
}
|
||||
foreach ($selected_langcodes as $langcode) {
|
||||
if ($entity instanceof TranslatableInterface) {
|
||||
$entity = $entity->getTranslation($langcode);
|
||||
// If the entity is the default translation then deleting it will
|
||||
// delete all the translations.
|
||||
if ($entity->isDefaultTranslation()) {
|
||||
$delete_entities[$id] = $entity;
|
||||
// If there are translations already marked for deletion then remove
|
||||
// them as they will be deleted anyway.
|
||||
unset($delete_translations[$id]);
|
||||
// Update the total count. Since a single delete will delete all
|
||||
// translations, we need to add the number of translations to the
|
||||
// count.
|
||||
$total_count += count($entity->getTranslationLanguages());
|
||||
}
|
||||
// Add the translation to the list of translations to be deleted
|
||||
// unless the default translation is being deleted.
|
||||
elseif (!isset($delete_entities[$id])) {
|
||||
$delete_translations[$id][] = $entity;
|
||||
}
|
||||
}
|
||||
elseif (!isset($delete_entities[$id])) {
|
||||
$delete_entities[$id] = $entity;
|
||||
$total_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete_entities) {
|
||||
$storage->delete($delete_entities);
|
||||
foreach ($delete_entities as $entity) {
|
||||
$this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label has been deleted.', [
|
||||
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
|
||||
'%label' => $entity->label(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete_translations) {
|
||||
/** @var \Drupal\Core\Entity\TranslatableInterface[][] $delete_translations */
|
||||
foreach ($delete_translations as $id => $translations) {
|
||||
$entity = $entities[$id]->getUntranslated();
|
||||
foreach ($translations as $translation) {
|
||||
$entity->removeTranslation($translation->language()->getId());
|
||||
}
|
||||
$entity->save();
|
||||
foreach ($translations as $translation) {
|
||||
$this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label @language translation has been deleted.', [
|
||||
'@entity-type' => $entity->getEntityType()->getLowercaseLabel(),
|
||||
'%label' => $entity->label(),
|
||||
'@language' => $translation->language()->getName(),
|
||||
]);
|
||||
}
|
||||
$total_count += count($translations);
|
||||
}
|
||||
}
|
||||
|
||||
if ($total_count) {
|
||||
$this->messenger->addStatus($this->getDeletedMessage($total_count));
|
||||
}
|
||||
if ($inaccessible_entities) {
|
||||
$this->messenger->addWarning($this->getInaccessibleMessage(count($inaccessible_entities)));
|
||||
}
|
||||
$this->tempStore->delete($this->currentUser->id());
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message to show the user after an item was deleted.
|
||||
*
|
||||
* @param int $count
|
||||
* Count of deleted translations.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* The item deleted message.
|
||||
*/
|
||||
protected function getDeletedMessage($count) {
|
||||
return $this->formatPlural($count, 'Deleted @count item.', 'Deleted @count items.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message to show the user when an item has not been deleted.
|
||||
*
|
||||
* @param int $count
|
||||
* Count of deleted translations.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* The item inaccessible message.
|
||||
*/
|
||||
protected function getInaccessibleMessage($count) {
|
||||
return $this->formatPlural($count, "@count item has not been deleted because you do not have the necessary permissions.", "@count items have not been deleted because you do not have the necessary permissions.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\entity\Entity\RevisionableEntityBundleInterface;
|
||||
|
||||
/**
|
||||
* Extends the base entity form with revision support in the UI.
|
||||
*/
|
||||
class RevisionableContentEntityForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* The entity being used by this form.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\entity\Revision\EntityRevisionLogInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareEntity() {
|
||||
parent::prepareEntity();
|
||||
|
||||
$bundle_entity = $this->getBundleEntity();
|
||||
|
||||
// Set up default values, if required.
|
||||
if (!$this->entity->isNew()) {
|
||||
$this->entity->setRevisionLogMessage(NULL);
|
||||
}
|
||||
|
||||
if ($bundle_entity instanceof RevisionableEntityBundleInterface) {
|
||||
// Always use the default revision setting.
|
||||
$this->entity->setNewRevision($bundle_entity && $bundle_entity->shouldCreateNewRevision());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bundle entity of the entity, or NULL if there is none.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
*/
|
||||
protected function getBundleEntity() {
|
||||
if ($bundle_key = $this->entity->getEntityType()->getKey('bundle')) {
|
||||
return $this->entity->{$bundle_key}->referencedEntities()[0];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$entity_type = $this->entity->getEntityType();
|
||||
$bundle_entity = $this->getBundleEntity();
|
||||
$account = $this->currentUser();
|
||||
|
||||
if ($this->operation == 'edit') {
|
||||
$form['#title'] = $this->t('Edit %bundle_label @label', [
|
||||
'%bundle_label' => $bundle_entity ? $bundle_entity->label() : '',
|
||||
'@label' => $this->entity->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
$form['advanced'] = [
|
||||
'#type' => 'vertical_tabs',
|
||||
'#weight' => 99,
|
||||
];
|
||||
|
||||
// Add a log field if the "Create new revision" option is checked, or if the
|
||||
// current user has the ability to check that option.
|
||||
// @todo Could we autogenerate this form by using some widget on the
|
||||
// revision info field.
|
||||
$form['revision_information'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Revision information'),
|
||||
// Open by default when "Create new revision" is checked.
|
||||
'#open' => $this->entity->isNewRevision(),
|
||||
'#group' => 'advanced',
|
||||
'#weight' => 20,
|
||||
'#access' => $this->entity->isNewRevision() || $account->hasPermission($entity_type->get('admin_permission')),
|
||||
];
|
||||
|
||||
$form['revision_information']['revision'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Create new revision'),
|
||||
'#default_value' => $this->entity->isNewRevision(),
|
||||
'#access' => $account->hasPermission($entity_type->get('admin_permission')),
|
||||
];
|
||||
|
||||
// Check the revision log checkbox when the log textarea is filled in.
|
||||
// This must not happen if "Create new revision" is enabled by default,
|
||||
// since the state would auto-disable the checkbox otherwise.
|
||||
if (!$this->entity->isNewRevision()) {
|
||||
$form['revision_information']['revision']['#states'] = [
|
||||
'checked' => [
|
||||
'textarea[name="revision_log"]' => ['empty' => FALSE],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$form['revision_information']['revision_log'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Revision log message'),
|
||||
'#rows' => 4,
|
||||
'#default_value' => $this->entity->getRevisionLogMessage(),
|
||||
'#description' => $this->t('Briefly describe the changes you have made.'),
|
||||
];
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
// Save as a new revision if requested to do so.
|
||||
if (!$form_state->isValueEmpty('revision')) {
|
||||
$this->entity->setNewRevision();
|
||||
}
|
||||
|
||||
$insert = $this->entity->isNew();
|
||||
$this->entity->save();
|
||||
$context = ['@type' => $this->entity->bundle(), '%info' => $this->entity->label()];
|
||||
$logger = $this->logger('content');
|
||||
$bundle_entity = $this->getBundleEntity();
|
||||
$t_args = ['@type' => $bundle_entity ? $bundle_entity->label() : 'None', '%info' => $this->entity->label()];
|
||||
|
||||
if ($insert) {
|
||||
$logger->notice('@type: added %info.', $context);
|
||||
drupal_set_message($this->t('@type %info has been created.', $t_args));
|
||||
}
|
||||
else {
|
||||
$logger->notice('@type: updated %info.', $context);
|
||||
drupal_set_message($this->t('@type %info has been updated.', $t_args));
|
||||
}
|
||||
|
||||
if ($this->entity->id()) {
|
||||
$form_state->setValue('id', $this->entity->id());
|
||||
$form_state->set('id', $this->entity->id());
|
||||
|
||||
if ($this->entity->getEntityType()->hasLinkTemplate('collection')) {
|
||||
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
|
||||
}
|
||||
else {
|
||||
$form_state->setRedirectUrl($this->entity->toUrl('canonical'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// In the unlikely case something went wrong on save, the entity will be
|
||||
// rebuilt and entity form redisplayed.
|
||||
drupal_set_message($this->t('The entity could not be saved.'), 'error');
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Menu;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Menu\LocalActionDefault;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Provides a local action to add an entity.
|
||||
*/
|
||||
class EntityAddLocalAction extends LocalActionDefault {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity type.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* Constructs a EntityAddLocalAction object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider to load routes by name.
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, EntityTypeInterface $entity_type, TranslationInterface $string_translation) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider);
|
||||
|
||||
$this->entityType = $entity_type;
|
||||
$this->setStringTranslation($string_translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
/* @var \Drupal\Core\Entity\EntityTypeManagerInterface */
|
||||
$entity_type_manager = $container->get('entity_type.manager');
|
||||
// The plugin ID is of the form
|
||||
// "entity.entity_actions:entity.$entity_type_id.collection".
|
||||
// @see entity.links.action.yml
|
||||
// @see \Drupal\entity\Menu\EntityCollectionLocalActionProvider::buildLocalActions()
|
||||
list(, $derivate_id) = explode(':', $plugin_id);
|
||||
list(, $entity_type_id, ) = explode('.', $derivate_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('router.route_provider'),
|
||||
$entity_type_manager->getDefinition($entity_type_id),
|
||||
$container->get('string_translation')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle(Request $request = NULL) {
|
||||
return (string) $this->t('Add @entity', [
|
||||
'@entity' => (string) $this->entityType->getSingularLabel(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Menu;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides a action link to the add page or add form on the collection.
|
||||
*/
|
||||
class EntityCollectionLocalActionProvider implements EntityLocalActionProviderInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildLocalActions(EntityTypeInterface $entity_type) {
|
||||
$actions = [];
|
||||
if ($entity_type->hasLinkTemplate('collection')) {
|
||||
$entity_type_id = $entity_type->id();
|
||||
|
||||
if ($entity_type->hasLinkTemplate('add-page')) {
|
||||
$route_name = "entity.$entity_type_id.add_page";
|
||||
}
|
||||
elseif ($entity_type->hasLinkTemplate('add-form')) {
|
||||
$route_name = "entity.$entity_type_id.add_form";
|
||||
}
|
||||
|
||||
if (isset($route_name)) {
|
||||
$actions[$route_name] = [
|
||||
// The title is translated at runtime by EntityAddLocalAction.
|
||||
/* @see \Drupal\entity\Menu\EntityAddLocalAction::getTitle() */
|
||||
'title' => 'Add ' . $entity_type->getSingularLabel(),
|
||||
'route_name' => $route_name,
|
||||
'appears_on' => ["entity.$entity_type_id.collection"],
|
||||
'class' => EntityAddLocalAction::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Menu;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for entity local action providers.
|
||||
*/
|
||||
interface EntityLocalActionProviderInterface {
|
||||
|
||||
/**
|
||||
* Builds local actions for the given entity type.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return array[]
|
||||
* An array of local action definitions.
|
||||
*/
|
||||
public function buildLocalActions(EntityTypeInterface $entity_type);
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace Drupal\entity\Plugin\Action;
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\PrivateTempStoreFactory;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface
|
||||
/**
|
||||
* The tempstore object.
|
||||
*
|
||||
* @var \Drupal\user\SharedTempStore
|
||||
* @var \Drupal\Core\TempStore\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
@@ -42,7 +42,7 @@ class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param AccountInterface $current_user
|
||||
* Current user.
|
||||
@@ -62,7 +62,7 @@ class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('user.private_tempstore'),
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
@@ -71,13 +71,13 @@ class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $entities) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
|
||||
$selection = [];
|
||||
foreach ($entities as $entity) {
|
||||
$langcode = $entity->language()->getId();
|
||||
$selection[$entity->id()][$langcode] = $langcode;
|
||||
}
|
||||
$this->tempStore->set($this->currentUser->id(), $selection);
|
||||
$this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterfa
|
||||
$definitions = [];
|
||||
foreach ($this->getParticipatingEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['label'] = t('Delete @entity_type', ['@entity_type' => $entity_type->getLowercaseLabel()]);
|
||||
$definition['label'] = t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]);
|
||||
$definition['type'] = $entity_type_id;
|
||||
$definition['confirm_form_route_name'] = 'entity.' . $entity_type_id . '.delete_multiple_form';
|
||||
$definitions[$entity_type_id] = $definition;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Plugin\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Derives local actions for entity types.
|
||||
*/
|
||||
class EntityActionsDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs an entity local actions deriver.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static($container->get('entity_type.manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
if (!$this->derivatives) {
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
$handlers = $entity_type->getHandlerClasses();
|
||||
if (isset($handlers['local_action_provider'])) {
|
||||
foreach ($handlers['local_action_provider'] as $class) {
|
||||
/** @var \Drupal\entity\Menu\EntityLocalActionProviderInterface $handler */
|
||||
$handler = $this->entityTypeManager->createHandlerInstance($class, $entity_type);
|
||||
$this->derivatives += $handler->buildLocalActions($entity_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,12 +13,8 @@ abstract class RevisionableContentEntityBase extends BaseRevisionableContentEnti
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function urlRouteParameters($rel) {
|
||||
$uri_route_parameters = [];
|
||||
$uri_route_parameters = parent::urlRouteParameters($rel);
|
||||
|
||||
if ($rel != 'collection') {
|
||||
// The entity ID is needed as a route parameter.
|
||||
$uri_route_parameters[$this->getEntityTypeId()] = $this->id();
|
||||
}
|
||||
if (strpos($this->getEntityType()->getLinkTemplate($rel), $this->getEntityTypeId() . '_revision') !== FALSE) {
|
||||
$uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider as CoreAdminHtmlRouteProvider;
|
||||
|
||||
/**
|
||||
* Provides HTML routes for entities with administrative add/edit/delete pages.
|
||||
*/
|
||||
class AdminHtmlRouteProvider extends CoreAdminHtmlRouteProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCollectionRoute(EntityTypeInterface $entity_type) {
|
||||
$route = parent::getCollectionRoute($entity_type);
|
||||
if ($route && $entity_type->hasHandlerClass('permission_provider')) {
|
||||
$admin_permission = $entity_type->getAdminPermission();
|
||||
$overview_permission = "access {$entity_type->id()} overview";
|
||||
$route->setRequirement('_permission', "$admin_permission+$overview_permission");
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\entity\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider as CoreDefaultHtmlRouteProvider;
|
||||
|
||||
/**
|
||||
* Provides HTML routes for entities.
|
||||
*/
|
||||
class DefaultHtmlRouteProvider extends CoreDefaultHtmlRouteProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCollectionRoute(EntityTypeInterface $entity_type) {
|
||||
$route = parent::getCollectionRoute($entity_type);
|
||||
if ($route && $entity_type->hasHandlerClass('permission_provider')) {
|
||||
$admin_permission = $entity_type->getAdminPermission();
|
||||
$overview_permission = "access {$entity_type->id()} overview";
|
||||
$route->setRequirement('_permission', "$admin_permission+$overview_permission");
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,9 +36,9 @@ class DeleteMultipleRouteProvider implements EntityRouteProviderInterface {
|
||||
protected function deleteMultipleFormRoute(EntityTypeInterface $entity_type) {
|
||||
if ($entity_type->hasLinkTemplate('delete-multiple-form')) {
|
||||
$route = new Route($entity_type->getLinkTemplate('delete-multiple-form'));
|
||||
$route->setDefault('_form', '\Drupal\entity\Form\DeleteMultiple');
|
||||
$route->setDefault('_form', '\Drupal\entity\Form\DeleteMultipleForm');
|
||||
$route->setDefault('entity_type_id', $entity_type->id());
|
||||
$route->setRequirement('_permission', $entity_type->getAdminPermission());
|
||||
$route->setRequirement('_entity_delete_multiple_access', $entity_type->id());
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,8 @@
|
||||
|
||||
namespace Drupal\entity;
|
||||
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides generic entity permissions which are cached per user.
|
||||
@@ -25,7 +20,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
* - create $bundle $entity_type
|
||||
*
|
||||
* As this class supports "view own ($bundle) $entity_type" it is just cacheable
|
||||
* per user, which might harm performance of sites. Given that please use
|
||||
* per user, which might harm performance of sites. Given that please use
|
||||
* \Drupal\entity\EntityPermissionProvider unless you need the feature, or your
|
||||
* entity type is not really user facing (commerce orders for example).
|
||||
*
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ package: Testing
|
||||
dependencies:
|
||||
- entity
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-09-20
|
||||
version: '8.x-1.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2018-03-12
|
||||
version: '8.x-1.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'entity'
|
||||
datestamp: 1505895847
|
||||
datestamp: 1520873296
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ package: Testing
|
||||
dependencies:
|
||||
- entity
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-09-20
|
||||
version: '8.x-1.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2018-03-12
|
||||
version: '8.x-1.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'entity'
|
||||
datestamp: 1505895847
|
||||
datestamp: 1520873296
|
||||
|
||||
+3
-3
@@ -3,8 +3,8 @@ type: module
|
||||
package: Testing
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-09-20
|
||||
version: '8.x-1.0-beta1'
|
||||
# Information added by Drupal.org packaging script on 2018-03-12
|
||||
version: '8.x-1.0-beta2'
|
||||
core: '8.x'
|
||||
project: 'entity'
|
||||
datestamp: 1505895847
|
||||
datestamp: 1520873296
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
entity.entity_test_enhanced.collection:
|
||||
path: '/entity_test_enhanced'
|
||||
defaults:
|
||||
_entity_list: 'entity_test_enhanced'
|
||||
_title: 'Entity test with enhancements'
|
||||
requirements:
|
||||
_permission: 'administer entity_test_enhanced'
|
||||
+14
-4
@@ -11,21 +11,31 @@ use Drupal\entity\Revision\RevisionableContentEntityBase;
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "entity_test_enhanced",
|
||||
* label = @Translation("Entity test with enhancements"),
|
||||
* label = @Translation("Enhanced entity"),
|
||||
* label_collection = @Translation("Enhanced entities"),
|
||||
* label_singular = @Translation("enhanced entity"),
|
||||
* label_plural = @Translation("enhanced entities"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count enhanced entity",
|
||||
* plural = "@count enhanced entities",
|
||||
* ),
|
||||
* handlers = {
|
||||
* "storage" = "\Drupal\Core\Entity\Sql\SqlContentEntityStorage",
|
||||
* "access" = "\Drupal\Core\Entity\EntityAccessControlHandler",
|
||||
* "permission_provider" = "\Drupal\entity\EntityPermissionProvider",
|
||||
* "form" = {
|
||||
* "add" = "\Drupal\entity\Form\RevisionableContentEntityForm",
|
||||
* "edit" = "\Drupal\entity\Form\RevisionableContentEntityForm",
|
||||
* "add" = "\Drupal\Core\Entity\ContentEntityForm",
|
||||
* "edit" = "\Drupal\Core\Entity\ContentEntityForm",
|
||||
* "delete" = "\Drupal\Core\Entity\EntityDeleteForm",
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
|
||||
* "html" = "\Drupal\entity\Routing\DefaultHtmlRouteProvider",
|
||||
* "revision" = "\Drupal\entity\Routing\RevisionRouteProvider",
|
||||
* "delete-multiple" = "\Drupal\entity\Routing\DeleteMultipleRouteProvider",
|
||||
* },
|
||||
* "local_action_provider" = {
|
||||
* "collection" = "\Drupal\entity\Menu\EntityCollectionLocalActionProvider",
|
||||
* },
|
||||
* "list_builder" = "\Drupal\Core\Entity\EntityListBuilder",
|
||||
* },
|
||||
* base_table = "entity_test_enhanced",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ namespace Drupal\entity_module_test\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
|
||||
use Drupal\Core\Entity\EntityDescriptionInterface;
|
||||
use Drupal\entity\Entity\RevisionableEntityBundleInterface;
|
||||
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
|
||||
|
||||
/**
|
||||
* Provides bundles for the test entity.
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\entity\Functional;
|
||||
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntity;
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
|
||||
use Drupal\Tests\block\Traits\BlockCreationTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the collection route access check.
|
||||
*
|
||||
* @group entity
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
*
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class CollectionRouteAccessTest extends BrowserTestBase {
|
||||
|
||||
use BlockCreationTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['entity_module_test', 'user', 'entity', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
EnhancedEntityBundle::create([
|
||||
'id' => 'default',
|
||||
'label' => 'Default',
|
||||
])->save();
|
||||
|
||||
$this->placeBlock('local_tasks_block');
|
||||
$this->placeBlock('system_breadcrumb_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the collection route access.
|
||||
*/
|
||||
public function testCollectionRouteAccess() {
|
||||
$entity = EnhancedEntity::create([
|
||||
'name' => 'rev 1',
|
||||
'type' => 'default',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
// User without any relevant permissions.
|
||||
$account = $this->drupalCreateUser(['access administration pages']);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$this->drupalGet($entity->toUrl('collection'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// User with "access overview" permissions.
|
||||
$account = $this->drupalCreateUser(['access entity_test_enhanced overview']);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$this->drupalGet($entity->toUrl('collection'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// User with full administration permissions.
|
||||
$account = $this->drupalCreateUser(['administer entity_test_enhanced']);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$this->drupalGet($entity->toUrl('collection'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -18,7 +18,7 @@ class DeleteMultipleFormTest extends BrowserTestBase {
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface;
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
@@ -60,13 +60,13 @@ class DeleteMultipleFormTest extends BrowserTestBase {
|
||||
$selection[$entity->id()][$langcode] = $langcode;
|
||||
}
|
||||
// Add the selection to the tempstore just like DeleteAction would.
|
||||
$tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
|
||||
$tempstore->set($this->account->id(), $selection);
|
||||
$tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm');
|
||||
$tempstore->set($this->account->id() . ':entity_test_enhanced', $selection);
|
||||
|
||||
$this->drupalGet('/entity_test_enhanced/delete');
|
||||
$assert = $this->assertSession();
|
||||
$assert->statusCodeEquals(200);
|
||||
$assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these items?');
|
||||
$assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these enhanced entities?');
|
||||
$delete_button = $this->getSession()->getPage()->findButton('Delete');
|
||||
$delete_button->click();
|
||||
$assert = $this->assertSession();
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\entity\Functional\Menu;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that entity local actions are generated correctly.
|
||||
*
|
||||
* @group entity
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
*
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class EntityLocalActionTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'entity', 'entity_module_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
$account = $this->drupalCreateUser(['administer entity_test_enhanced']);
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the local action on the collection is provided correctly.
|
||||
*/
|
||||
public function testCollectionLocalAction() {
|
||||
$this->drupalGet('/entity_test_enhanced');
|
||||
$this->assertSession()->linkByHrefExists('/entity_test_enhanced/add');
|
||||
$this->assertSession()->linkExists('Add enhanced entity');
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -4,7 +4,7 @@ namespace Drupal\Tests\entity\Functional;
|
||||
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntity;
|
||||
use Drupal\entity_module_test\Entity\EnhancedEntityBundle;
|
||||
use Drupal\simpletest\BlockCreationTrait;
|
||||
use Drupal\Tests\block\Traits\BlockCreationTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
@@ -75,7 +75,7 @@ class RevisionRouteAccessTest extends BrowserTestBase {
|
||||
$this->drupalGet('/entity_test_enhanced/1/revisions');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('Revisions');
|
||||
$collection_link = $this->getSession()->getPage()->findLink('Entity test with enhancements');
|
||||
$collection_link = $this->getSession()->getPage()->findLink('Enhanced entities');
|
||||
$collection_link->click();
|
||||
$this->assertSession()->addressEquals('/entity_test_enhanced');
|
||||
$this->assertSession()->responseContains('Edit');
|
||||
|
||||
@@ -74,8 +74,8 @@ class DeleteActionTest extends KernelTestBase {
|
||||
|
||||
$action->execute($entities);
|
||||
// Confirm that the entity ids and langcodes are now in the tempstore.
|
||||
$tempstore = \Drupal::service('user.private_tempstore')->get('entity_delete_multiple_confirm');
|
||||
$selection = $tempstore->get($this->user->id());
|
||||
$tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm');
|
||||
$selection = $tempstore->get($this->user->id() . ':entity_test_enhanced');
|
||||
$this->assertEquals(array_keys($entities), array_keys($selection));
|
||||
$this->assertEquals([['en' => 'en'], ['en' => 'en']], array_values($selection));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user