Browse Source

updated contrib modules

Bachir Soussi Chiadmi 7 years ago
parent
commit
272fa07ccf
100 changed files with 2697 additions and 883 deletions
  1. 24 1
      sites/all/modules/contrib/admin/admin_toolbar/CHANGELOG.txt
  2. 3 3
      sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.info.yml
  3. 1 0
      sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.libraries.yml
  4. 2 1
      sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.module
  5. 7 5
      sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_links_access_filter/admin_toolbar_links_access_filter.info.yml
  6. 3 3
      sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.info.yml
  7. 60 116
      sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.module
  8. 88 17
      sites/all/modules/contrib/admin/admin_toolbar/css/admin.toolbar.css
  9. 10 10
      sites/all/modules/contrib/admin/admin_toolbar/js/admin_toolbar.js
  10. 2 0
      sites/all/modules/contrib/admin/domain/.travis.yml
  11. 9 2
      sites/all/modules/contrib/admin/domain/CHANGELOG.md
  12. 3 3
      sites/all/modules/contrib/admin/domain/domain/domain.info.yml
  13. 4 5
      sites/all/modules/contrib/admin/domain/domain/domain.install
  14. 1 0
      sites/all/modules/contrib/admin/domain/domain/migrations/d7_domain.yml
  15. 0 1
      sites/all/modules/contrib/admin/domain/domain/src/DomainElementManager.php
  16. 1 1
      sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php
  17. 21 2
      sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php
  18. 0 2
      sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php
  19. 1 3
      sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php
  20. 2 1
      sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php
  21. 3 3
      sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml
  22. 3 5
      sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainEntityReferenceTest.php
  23. 63 0
      sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.views.schema.yml
  24. 3 3
      sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml
  25. 7 10
      sites/all/modules/contrib/admin/domain/domain_access/domain_access.module
  26. 1 1
      sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/argument/DomainAccessArgument.php
  27. 3 3
      sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml
  28. 3 1
      sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php
  29. 3 3
      sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml
  30. 3 3
      sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml
  31. 3 3
      sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml
  32. 3 3
      sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml
  33. 3 3
      sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml
  34. 10 13
      sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml
  35. 9 9
      sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml
  36. 8 0
      sites/all/modules/contrib/admin/domain/domain_content/config/schema/domain_content.views.schema.yml
  37. 3 3
      sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml
  38. 1 1
      sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php
  39. 9 0
      sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.views.schema.yml
  40. 3 3
      sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml
  41. 15 9
      sites/all/modules/contrib/admin/domain/domain_source/domain_source.module
  42. 32 0
      sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php
  43. 6 4
      sites/all/modules/contrib/admin/features/composer.json
  44. 24 43
      sites/all/modules/contrib/admin/features/config/schema/features.schema.yml
  45. 11 0
      sites/all/modules/contrib/admin/features/drush.services.yml
  46. 0 0
      sites/all/modules/contrib/admin/features/drush/features.drush8.inc
  47. 3 3
      sites/all/modules/contrib/admin/features/features.info.yml
  48. 3 3
      sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.info.yml
  49. 8 9
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesEditForm.php
  50. 1 4
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesExportForm.php
  51. 1 1
      sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesBundleUITest.php
  52. 1052 0
      sites/all/modules/contrib/admin/features/src/Commands/FeaturesCommands.php
  53. 7 0
      sites/all/modules/contrib/admin/features/src/Exception/DomainException.php
  54. 7 0
      sites/all/modules/contrib/admin/features/src/Exception/InvalidArgumentException.php
  55. 1 2
      sites/all/modules/contrib/admin/features/src/FeaturesAssigner.php
  56. 1 1
      sites/all/modules/contrib/admin/features/src/FeaturesConfigInstaller.php
  57. 9 1
      sites/all/modules/contrib/admin/features/src/FeaturesInstallStorage.php
  58. 36 20
      sites/all/modules/contrib/admin/features/src/FeaturesManager.php
  59. 14 6
      sites/all/modules/contrib/admin/features/src/Package.php
  60. 1 1
      sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentPackages.php
  61. 3 3
      sites/all/modules/contrib/admin/features/tests/modules/test_feature/test_feature.info.yml
  62. 2 0
      sites/all/modules/contrib/admin/features/tests/modules/test_mybundle_core/test_mybundle_core.features.yml
  63. 3 3
      sites/all/modules/contrib/admin/features/tests/modules/test_mybundle_core/test_mybundle_core.info.yml
  64. 22 1
      sites/all/modules/contrib/admin/features/tests/src/Kernel/FeaturesAssignTest.php
  65. 200 9
      sites/all/modules/contrib/admin/features/tests/src/Unit/FeaturesManagerTest.php
  66. 1 1
      sites/all/modules/contrib/dev/entity/.travis.yml
  67. 1 1
      sites/all/modules/contrib/dev/entity/composer.json
  68. 4 4
      sites/all/modules/contrib/dev/entity/entity.info.yml
  69. 2 0
      sites/all/modules/contrib/dev/entity/entity.links.action.yml
  70. 6 0
      sites/all/modules/contrib/dev/entity/entity.services.yml
  71. 87 0
      sites/all/modules/contrib/dev/entity/src/Access/EntityDeleteMultipleAccessCheck.php
  72. 6 14
      sites/all/modules/contrib/dev/entity/src/Controller/RevisionControllerTrait.php
  73. 0 17
      sites/all/modules/contrib/dev/entity/src/Entity/RevisionableEntityBundleInterface.php
  74. 0 6
      sites/all/modules/contrib/dev/entity/src/EntityPermissionProvider.php
  75. 0 37
      sites/all/modules/contrib/dev/entity/src/EntityViewBuilder.php
  76. 0 226
      sites/all/modules/contrib/dev/entity/src/Form/DeleteMultiple.php
  77. 323 0
      sites/all/modules/contrib/dev/entity/src/Form/DeleteMultipleForm.php
  78. 0 159
      sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php
  79. 81 0
      sites/all/modules/contrib/dev/entity/src/Menu/EntityAddLocalAction.php
  80. 41 0
      sites/all/modules/contrib/dev/entity/src/Menu/EntityCollectionLocalActionProvider.php
  81. 23 0
      sites/all/modules/contrib/dev/entity/src/Menu/EntityLocalActionProviderInterface.php
  82. 6 6
      sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php
  83. 1 1
      sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php
  84. 58 0
      sites/all/modules/contrib/dev/entity/src/Plugin/Derivative/EntityActionsDeriver.php
  85. 1 5
      sites/all/modules/contrib/dev/entity/src/Revision/RevisionableContentEntityBase.php
  86. 26 0
      sites/all/modules/contrib/dev/entity/src/Routing/AdminHtmlRouteProvider.php
  87. 26 0
      sites/all/modules/contrib/dev/entity/src/Routing/DefaultHtmlRouteProvider.php
  88. 2 2
      sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php
  89. 1 6
      sites/all/modules/contrib/dev/entity/src/UncacheableEntityPermissionProvider.php
  90. 3 3
      sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_examples_test/entity_module_bundle_plugin_examples_test.info.yml
  91. 3 3
      sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_test/entity_module_bundle_plugin_test.info.yml
  92. 3 3
      sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml
  93. 0 7
      sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.routing.yml
  94. 14 4
      sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php
  95. 1 1
      sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php
  96. 77 0
      sites/all/modules/contrib/dev/entity/tests/src/Functional/CollectionRouteAccessTest.php
  97. 4 4
      sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php
  98. 44 0
      sites/all/modules/contrib/dev/entity/tests/src/Functional/Menu/EntityLocalActionTest.php
  99. 2 2
      sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php
  100. 2 2
      sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php

+ 24 - 1
sites/all/modules/contrib/admin/admin_toolbar/CHANGELOG.txt

@@ -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.

+ 3 - 3
sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.info.yml

@@ -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

+ 1 - 0
sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.libraries.yml

@@ -7,3 +7,4 @@ toolbar.tree:
     js/admin_toolbar.js: {}
   dependencies:
     - core/jquery
+    - core/drupal

+ 2 - 1
sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.module

@@ -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
sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_links_access_filter/admin_toolbar_links_access_filter.info.yml

@@ -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
sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.info.yml

@@ -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
sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.module

@@ -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) {
-
-    // 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;
+  // 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',
+    ];
+  }
 
-      // 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,
+    ];
+  }
 }
 
 /**

+ 88 - 17
sites/all/modules/contrib/admin/admin_toolbar/css/admin.toolbar.css

@@ -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 {
-  position: absolute;
-  padding-top: 0;
-  top: 0;
-  right: 200px;
-  width: 200px;
+[dir="rtl"] .toolbar-tray-horizontal .menu-item:hover {
+  background: #fff;
 }
 
-[dir="rtl"] .toolbar-tray-horizontal li:hover ul li {
-  float: none !important;
+[dir="rtl"] .toolbar-tray-horizontal .menu-item a:focus {
+  background: #abeae4;
 }
 
-[dir="rtl"] .toolbar-tray-horizontal li:focus ul li {
-  float: none !important;
+[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 .toolbar-menu ul .toolbar-icon {
-  padding-left: 1.3333em;
+[dir="rtl"] .toolbar-tray-horizontal .menu-item--expanded .menu {
+  background: #fff;
+  width: auto;
+  height: auto;
 }
 
-[dir="rtl"] .toolbar-tray-horizontal ul li.menu-item--expanded .menu-item:hover ul {
-  margin: -40px 197px 0 0;
+[dir="rtl"] .toolbar-tray-horizontal .menu-item--expanded {
+  background-color: #f5f5f2;
 }
 
-[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 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;
+  width: 200px;
+  box-shadow: 2px 2px 3px hsla(0, 0%, 0%, 0.4);
+  z-index: 1;
+}
+
+[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;
+}

+ 10 - 10
sites/all/modules/contrib/admin/admin_toolbar/js/admin_toolbar.js

@@ -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);

+ 2 - 0
sites/all/modules/contrib/admin/domain/.travis.yml

@@ -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

+ 9 - 2
sites/all/modules/contrib/admin/domain/CHANGELOG.md

@@ -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

+ 3 - 3
sites/all/modules/contrib/admin/domain/domain/domain.info.yml

@@ -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

+ 4 - 5
sites/all/modules/contrib/admin/domain/domain/domain.install

@@ -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 - 0
sites/all/modules/contrib/admin/domain/domain/migration_templates/d7_domain.yml → sites/all/modules/contrib/admin/domain/domain/migrations/d7_domain.yml

@@ -15,3 +15,4 @@ process:
   status: valid
 destination:
   plugin: entity:domain
+  destination_module: domain

+ 0 - 1
sites/all/modules/contrib/admin/domain/domain/src/DomainElementManager.php

@@ -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();

+ 1 - 1
sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php

@@ -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
sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainServerBlock.php

@@ -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'),

+ 0 - 2
sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainSwitcherBlock.php

@@ -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 */

+ 1 - 3
sites/all/modules/contrib/admin/domain/domain/src/Plugin/Block/DomainTokenBlock.php

@@ -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
sites/all/modules/contrib/admin/domain/domain/src/Plugin/migrate/source/d7/DomainRecord.php

@@ -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
sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain/tests/src/Functional/DomainEntityReferenceTest.php

@@ -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
sites/all/modules/contrib/admin/domain/domain_access/config/schema/domain_access.views.schema.yml

@@ -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'

+ 3 - 3
sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml

@@ -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

+ 7 - 10
sites/all/modules/contrib/admin/domain/domain_access/domain_access.module

@@ -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
sites/all/modules/contrib/admin/domain/domain_access/src/Plugin/views/argument/DomainAccessArgument.php

@@ -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
sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_access/tests/src/Functional/DomainAccessFieldTest.php

@@ -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.');

+ 3 - 3
sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_alpha/domain_alpha.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_content/config/optional/views.view.domain_content_editors.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_content/config/schema/domain_content.views.schema.yml

@@ -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'

+ 3 - 3
sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml

@@ -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
sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php

@@ -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
sites/all/modules/contrib/admin/domain/domain_source/config/schema/domain_source.views.schema.yml

@@ -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'

+ 3 - 3
sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml

@@ -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

+ 15 - 9
sites/all/modules/contrib/admin/domain/domain_source/domain_source.module

@@ -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 - 0
sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php

@@ -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.');
   }
 }

+ 6 - 4
sites/all/modules/contrib/admin/features/composer.json

@@ -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"
 }

+ 24 - 43
sites/all/modules/contrib/admin/features/config/schema/features.schema.yml

@@ -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"

+ 11 - 0
sites/all/modules/contrib/admin/features/drush.services.yml

@@ -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 }

+ 0 - 0
sites/all/modules/contrib/admin/features/drush/features.drush.inc → sites/all/modules/contrib/admin/features/drush/features.drush8.inc


+ 3 - 3
sites/all/modules/contrib/admin/features/features.info.yml

@@ -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

+ 3 - 3
sites/all/modules/contrib/admin/features/modules/features_ui/features_ui.info.yml

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesEditForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Form/FeaturesExportForm.php

@@ -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
sites/all/modules/contrib/admin/features/modules/features_ui/src/Tests/FeaturesBundleUITest.php

@@ -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);
   }
 
   /**

+ 1052 - 0
sites/all/modules/contrib/admin/features/src/Commands/FeaturesCommands.php

@@ -0,0 +1,1052 @@
+<?php
+
+namespace Drupal\features\Commands;
+
+use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
+use Drupal\Component\Diff\DiffFormatter;
+use Drupal\config_update\ConfigDiffInterface;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\features\Exception\DomainException;
+use Drupal\features\Exception\InvalidArgumentException;
+use Drupal\features\FeaturesAssignerInterface;
+use Drupal\features\FeaturesGeneratorInterface;
+use Drupal\features\FeaturesManagerInterface;
+use Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite;
+use Drush\Commands\DrushCommands;
+use Drush\Exceptions\UserAbortException;
+use Drush\Utils\StringUtils;
+
+/**
+ * Drush commands for Features.
+ */
+class FeaturesCommands extends DrushCommands {
+
+  const OPTIONS =[
+    'bundle' => NULL,
+  ];
+
+  const OPTIONS_ADD = self::OPTIONS;
+
+  const OPTIONS_COMPONENTS = self::OPTIONS + [
+    'exported' => NULL,
+    'format' => 'table',
+    'not-exported' => NULL,
+  ];
+
+  const OPTIONS_DIFF = self::OPTIONS + [
+    'ctypes' => NULL,
+    'lines' => NULL,
+  ];
+
+  const OPTIONS_EXPORT = self::OPTIONS + [
+    'add-profile' => NULL,
+  ];
+
+  const OPTIONS_IMPORT = self::OPTIONS + [
+    'force' => NULL,
+  ];
+
+  const OPTIONS_IMPORT_ALL = self::OPTIONS;
+
+  const OPTIONS_LIST = self::OPTIONS + [
+    'format' => 'table',
+  ];
+
+  const OPTIONS_STATUS = self::OPTIONS;
+
+  /**
+   * The features_assigner service.
+   *
+   * @var \Drupal\features\FeaturesAssignerInterface
+   */
+  protected $assigner;
+
+  /**
+   * The features.manager service.
+   *
+   * @var \Drupal\features\FeaturesManagerInterface
+   */
+  protected $manager;
+
+  /**
+   * The features_generator service.
+   *
+   * @var \Drupal\features\FeaturesGeneratorInterface
+   */
+  protected $generator;
+
+  /**
+   * The config_update.config_diff service.
+   *
+   * @var \Drupal\config_update\ConfigDiffInterface
+   */
+  protected $configDiff;
+
+  /**
+   * The config.storage service.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $configStorage;
+
+  /**
+   * FeaturesCommands constructor.
+   *
+   * @param \Drupal\features\FeaturesAssignerInterface $assigner
+   *   The features_assigner service.
+   * @param \Drupal\features\FeaturesManagerInterface $manager
+   *   The features.manager service.
+   * @param \Drupal\features\FeaturesGeneratorInterface $generator
+   *   The features_generator service.
+   * @param \Drupal\config_update\ConfigDiffInterface $configDiff
+   *   The config_update.config_diff service.
+   * @param \Drupal\Core\Config\StorageInterface $configStorage
+   *   The config.storage service.
+   */
+  public function __construct(
+    FeaturesAssignerInterface $assigner,
+    FeaturesManagerInterface $manager,
+    FeaturesGeneratorInterface $generator,
+    ConfigDiffInterface $configDiff,
+    StorageInterface $configStorage
+  ) {
+    parent::__construct();
+    $this->assigner = $assigner;
+    $this->configDiff = $configDiff;
+    $this->configStorage = $configStorage;
+    $this->generator = $generator;
+    $this->manager = $manager;
+  }
+
+  /**
+   * Applies global options for Features drush commands, including the bundle.
+   *
+   * The option --name="bundle_name" sets the bundle namespace.
+   *
+   * @return \Drupal\features\FeaturesAssignerInterface
+   *   The features.assigner with options applied.
+   */
+  protected function featuresOptions(array $options) {
+    $bundleName = $this->getOption($options, 'bundle');
+    if (!empty($bundleName)) {
+      $bundle = $this->assigner->applyBundle($bundleName);
+      if ($bundle->getMachineName() !== $bundleName) {
+        $this->logger()->warning('Bundle {name} not found. Using default.', [
+          'name' => $bundleName,
+        ]);
+      }
+    }
+    else {
+      $this->assigner->assignConfigPackages();
+    }
+    return $this->assigner;
+  }
+
+  /**
+   * Get the value of an option.
+   *
+   * @param array $options
+   *   The options array.
+   * @param string $name
+   *   The option name.
+   * @param mixed $default
+   *   The default value of the option.
+   *
+   * @return mixed|null
+   *   The option value, defaulting to NULL.
+   */
+  protected function getOption(array $options, $name, $default = NULL) {
+    return isset($options[$name])
+      ? $options[$name]
+      : $default;
+  }
+
+  /**
+   * Display current Features settings.
+   *
+   * @param string $keys
+   *   A possibly empty, comma-separated, list of config information to display.
+   *
+   * @command features:status
+   *
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @aliases fs,features-status
+   */
+  public function status($keys = NULL, array $options = self::OPTIONS_STATUS) {
+    $this->featuresOptions($options);
+
+    $currentBundle = $this->assigner->getBundle();
+    $export_settings = $this->manager->getExportSettings();
+    $methods = $this->assigner->getEnabledAssigners();
+    $output = $this->output();
+    if ($currentBundle->isDefault()) {
+      $output->writeln(dt('Current bundle: none'));
+    }
+    else {
+      $output->writeln(dt('Current bundle: @name (@machine_name)', [
+        '@name' => $currentBundle->getName(),
+        '@machine_name' => $currentBundle->getMachineName(),
+      ]));
+    }
+    $output->writeln(dt('Export folder: @folder', [
+      '@folder' => $export_settings['folder'],
+    ]));
+    $output
+      ->writeln(dt('The following assignment methods are enabled:'));
+    $output->writeln(dt('  @methods', [
+      '@methods' => implode(', ', array_keys($methods)),
+    ]));
+
+    if (!empty($keys)) {
+      $config = $this->manager->getConfigCollection();
+      $keys = StringUtils::csvToArray($keys);
+      $data = count($keys) > 1
+        ? array_keys($config)
+        : $config[$keys[0]];
+      $output->writeln(print_r($data, TRUE));
+    }
+  }
+
+  /**
+   * Display a list of all generate-able existing features and packages.
+   *
+   * If a package name is provided as an argument, then all of the configuration
+   * objects assigned to that package will be listed.
+   *
+   * @param string $package_name
+   *   The package to list. Optional; if specified, lists all configuration
+   *   objects assigned to that package. If no package is specified, lists all
+   *   of the features.
+   *
+   * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields|bool
+   *   The command output, or FALSE if a requested package was not found.
+   *
+   * @command features:list:packages
+   *
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @usage drush features:list:packages
+   *   Display a list of all existing features and packages available to be
+   *   generated.
+   * @usage drush features:list:packages 'example_article'
+   *   Display a list of all configuration objects assigned to the
+   *   'example_article' package.
+   *
+   * @field-labels
+   *   config: Config
+   *   name: Name
+   *   machine_name: Machine name
+   *   status: Status
+   *   version: Version
+   *   state: State
+   *
+   * @aliases fl,features-list-packages
+   */
+  public function listPackages($package_name = NULL, $options = self::OPTIONS_LIST) {
+    $assigner = $this->featuresOptions($options);
+    $current_bundle = $assigner->getBundle();
+    $namespace = $current_bundle->isDefault() ? '' : $current_bundle->getMachineName();
+
+    $manager = $this->manager;
+    $packages = $manager->getPackages();
+
+    $packages = $manager->filterPackages($packages, $namespace);
+    $result = [];
+
+    // If no package was specified, list all packages.
+    if (empty($package_name)) {
+      foreach ($packages as $package) {
+        $overrides = $manager->detectOverrides($package);
+        $state = $package->getState();
+        if (!empty($overrides) && ($package->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT)) {
+          $state = FeaturesManagerInterface::STATE_OVERRIDDEN;
+        }
+
+        $packageState = ($state != FeaturesManagerInterface::STATE_DEFAULT)
+          ? $manager->stateLabel($state)
+          : '';
+
+        $result[$package->getMachineName()] = [
+          'name' => $package->getName(),
+          'machine_name' => $package->getMachineName(),
+          'status' => $manager->statusLabel($package->getStatus()),
+          'version' => $package->getVersion(),
+          'state' => $packageState,
+        ];
+      }
+      return new RowsOfFields($result);
+    }
+
+    // A valid package was listed.
+    $package = $this->manager->findPackage($package_name);
+
+    // If no matching package found, return an error.
+    if (empty($package)) {
+      $this->logger()->warning(dt('Package "@package" not found.', [
+        '@package' => $package_name,
+      ]));
+      return FALSE;
+    }
+
+    // This is a valid package, list its configuration.
+    $config = array_map(function ($name) {
+      return ['config' => $name];
+    }, $package->getConfig());
+
+    return new RowsOfFields($config);
+  }
+
+  /**
+   * Import module config from all installed features.
+   *
+   * @command features:import:all
+   *
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @usage drush features-import-all
+   *   Import module config from all installed features.
+   *
+   * @aliases fra,fia,fim-all,features-import-all
+   */
+  public function importAll($options = self::OPTIONS_IMPORT_ALL) {
+    $assigner = $this->featuresOptions($options);
+    $currentBundle = $assigner->getBundle();
+    $namespace = $currentBundle->isDefault() ? '' : $currentBundle->getMachineName();
+
+    $manager = $this->manager;
+    $packages = $manager->getPackages();
+    $packages = $manager->filterPackages($packages, $namespace);
+    $overridden = [];
+
+    foreach ($packages as $package) {
+      $overrides = $manager->detectOverrides($package);
+      $missing = $manager->detectMissing($package);
+      if ((!empty($missing) || !empty($overrides)) && ($package->getStatus() == FeaturesManagerInterface::STATUS_INSTALLED)) {
+        $overridden[] = $package->getMachineName();
+      }
+    }
+
+    if (!empty($overridden)) {
+      $this->import($overridden);
+    }
+    else {
+      $this->logger->info(dt('Current state already matches active config, aborting.'));
+    }
+  }
+
+  /**
+   * Export the configuration on your site into a custom module.
+   *
+   * @param array $packages
+   *   A list of features to export.
+   *
+   * @command features:export
+   *
+   * @option add-profile Package features into an install profile.
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @usage drush features-export
+   *   Export all available packages.
+   * @usage drush features-export example_article example_page
+   *   Export the example_article and example_page packages.
+   * @usage drush features-export --add-profile
+   *   Export all available packages and add them to an install profile.
+   *
+   * @aliases fex,fu,fua,fu-all,features-export
+   *
+   * @throws \Drupal\features\Exception\DomainException
+   * @throws \Drupal\features\Exception\InvalidArgumentException
+   * @throws \Drush\Exceptions\UserAbortException
+   * @throws \Exception
+   */
+  public function export(array $packages, $options = self::OPTIONS_EXPORT) {
+    $assigner = $this->featuresOptions($options);
+    $manager = $this->manager;
+    $generator = $this->generator;
+
+    $current_bundle = $assigner->getBundle();
+
+    if ($options['add-profile']) {
+      if ($current_bundle->isDefault) {
+        throw new InvalidArgumentException(dt("Must specify a profile name with --name"));
+      }
+      $current_bundle->setIsProfile(TRUE);
+    }
+
+    $all_packages = $manager->getPackages();
+    foreach ($packages as $name) {
+      if (!isset($all_packages[$name])) {
+        throw new DomainException(dt("The package @name does not exist.", [
+          '@name' => $name,
+        ]));
+      }
+    }
+
+    if (empty($packages)) {
+      $packages = $all_packages;
+      $dt_args = ['@modules' => implode(', ', array_keys($packages))];
+      drush_print(dt('The following extensions will be exported: @modules',
+        $dt_args));
+      if (!$this->io()->confirm('Do you really want to continue?')) {
+        throw new UserAbortException();
+      }
+    }
+    else {
+      $packages = array_combine($packages, $packages);
+    }
+
+    // If any packages exist, confirm before overwriting.
+    if ($existing_packages = $manager->listPackageDirectories($packages,
+      $current_bundle)) {
+      foreach ($existing_packages as $name => $directory) {
+        drush_print(dt("The extension @name already exists at @directory.",
+          ['@name' => $name, '@directory' => $directory]));
+      }
+      // Apparently, format_plural is not always available.
+      if (count($existing_packages) == 1) {
+        $message = dt('Would you like to overwrite it?');
+      }
+      else {
+        $message = dt('Would you like to overwrite them?');
+      }
+      if (!$this->io()->confirm($message)) {
+        throw new UserAbortException();
+      }
+    }
+
+    // Use the write generation method.
+    $method_id = FeaturesGenerationWrite::METHOD_ID;
+    $result = $generator->generatePackages($method_id, $current_bundle, array_keys($packages));
+
+    foreach ($result as $message) {
+      $method = $message['success'] ? 'success' : 'error';
+      $this->logger()->$method(dt($message['message'], $message['variables']));
+    }
+  }
+
+  /**
+   * Add a config item to a feature package.
+   *
+   * @param array|null $components
+   *   Patterns of config to add, see features:components for the format to use.
+   *
+   * @command features:add
+   *
+   * @todo @param $feature Feature package to export and add config to.
+   *
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @aliases fa,fe,features-add
+   *
+   * @throws \Drush\Exceptions\UserAbortException
+   * @throws \Exception
+   */
+  public function add($components = NULL, $options = self::OPTIONS_ADD) {
+    if ($components) {
+      $assigner = $this->featuresOptions($options);
+      $manager = $this->manager;
+      $generator = $this->generator;
+
+      $current_bundle = $assigner->getBundle();
+
+      $module = array_shift($args);
+      if (empty($args)) {
+        throw new \Exception('No components supplied.');
+      }
+      $components = $this->componentList();
+      $options = [
+        'exported' => FALSE,
+      ];
+
+      $filtered_components = $this->componentFilter($components, $args,
+        $options);
+      $items = $filtered_components['components'];
+
+      if (empty($items)) {
+        throw new \Exception('No components to add.');
+      }
+
+      $packages = [$module];
+      // If any packages exist, confirm before overwriting.
+      if ($existing_packages = $manager->listPackageDirectories($packages)) {
+        foreach ($existing_packages as $name => $directory) {
+          drush_print(dt("The extension @name already exists at @directory.",
+            ['@name' => $name, '@directory' => $directory]));
+        }
+        // Apparently, format_plural is not always available.
+        if (count($existing_packages) == 1) {
+          $message = dt('Would you like to overwrite it?');
+        }
+        else {
+          $message = dt('Would you like to overwrite them?');
+        }
+        if (!$this->io()->confirm($message)) {
+          throw new UserAbortException();
+        }
+      }
+      else {
+        $package = $manager->initPackage($module, NULL, '', 'module',
+          $current_bundle);
+        list($full_name, $path) = $manager->getExportInfo($package,
+          $current_bundle);
+        drush_print(dt('Will create a new extension @name in @directory',
+          ['@name' => $full_name, '@directory' => $path]));
+        if (!$this->io()->confirm(dt('Do you really want to continue?'))) {
+          throw new UserAbortException();
+        }
+      }
+
+      $config = $this->buildConfig($items);
+
+      $manager->assignConfigPackage($module, $config);
+
+      // Use the write generation method.
+      $method_id = FeaturesGenerationWrite::METHOD_ID;
+      $result = $generator->generatePackages($method_id, $current_bundle,
+        $packages);
+
+      foreach ($result as $message) {
+        $method = $message['success'] ? 'success' : 'error';
+        $this->logger()->$method(dt($message['message'],
+          $message['variables']));
+      }
+    }
+    else {
+      throw new \Exception('No feature name given.');
+    }
+  }
+
+  /**
+   * List features components.
+   *
+   * @param array $patterns
+   *   The components types to list. Omit this argument to list them all.
+   *
+   * @command features:components
+   *
+   * @option exported Show only components that have been exported.
+   * @option not-exported Show only components that have not been exported.
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @aliases fc,features-components
+   *
+   * @field-labels
+   *  source: Available sources
+   *
+   * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields|null
+   *   The command output. May be empty.
+   */
+  public function components(array $patterns, $options = self::OPTIONS_COMPONENTS) {
+    $args = $patterns;
+    $this->featuresOptions($options);
+
+    $components = $this->componentList();
+    ksort($components);
+    // If no args supplied, prompt with a list.
+    if (empty($args)) {
+      $types = array_keys($components);
+      array_unshift($types, 'all');
+      $choice = $this->io()
+        ->choice('Enter a number to choose which component type to list.', $types);
+      if ($choice === FALSE) {
+        return NULL;
+      }
+
+      $args = ($choice == 0) ? ['*'] : [$types[$choice]];
+    }
+    $options = [
+      'provided by' => TRUE,
+    ];
+    if ($options['exported']) {
+      $options['not exported'] = FALSE;
+    }
+    elseif ($options['not-exported']) {
+      $options['exported'] = FALSE;
+    }
+
+    $filtered_components = $this->componentFilter($components, $args, $options);
+    if ($filtered_components) {
+      return $this->componentPrint($filtered_components);
+    }
+  }
+
+  /**
+   * Show the difference between active|default config from a feature package.
+   *
+   * @param string $feature
+   *   The feature in question.
+   *
+   * @command features:diff
+   *
+   * @option ctypes Comma-separated list of component types to limit the output
+   *   to. Defaults to all types.
+   * @option lines Generate diffs with <n> lines of context instead of the
+   *   usual two.
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @aliases fd,features-diff
+   *
+   * @throws \Exception
+   */
+  public function diff($feature, $options = self::OPTIONS_DIFF) {
+    $manager = $this->manager;
+    $assigner = $this->featuresOptions($options);
+    $assigner->assignConfigPackages();
+
+    $module = $feature;
+
+    // @FIXME Actually do something with the "ctypes" option.
+    $filter_ctypes = $options['ctypes'];
+    if ($filter_ctypes) {
+      $filter_ctypes = explode(',', $filter_ctypes);
+    }
+
+    $feature = $manager->loadPackage($module, TRUE);
+    if (empty($feature)) {
+      throw new DomainException(dt('No such feature is available: @module', [
+        '@module' => $module,
+      ]));
+    }
+
+    $lines = $options['lines'];
+    $lines = isset($lines) ? $lines : 2;
+
+    $formatter = new DiffFormatter();
+    $formatter->leading_context_lines = $lines;
+    $formatter->trailing_context_lines = $lines;
+    $formatter->show_header = FALSE;
+
+    if (drush_get_context('DRUSH_NOCOLOR')) {
+      $red = $green = "%s";
+    }
+    else {
+      $red = "\033[31;40m\033[1m%s\033[0m";
+      $green = "\033[0;32;40m\033[1m%s\033[0m";
+    }
+
+    $overrides = $manager->detectOverrides($feature);
+    $missing = $manager->reorderMissing($manager->detectMissing($feature));
+    $overrides = array_merge($overrides, $missing);
+
+    $output = $this->output();
+
+    if (empty($overrides)) {
+      $output->writeln(dt('Active config matches stored config for @module.', [
+        '@module' => $module,
+      ]));
+    }
+    else {
+      $config_diff = $this->configDiff;
+
+      // Print key for colors.
+      $output->writeln(dt('Legend: '));
+      $output->writeln(sprintf($red,
+        dt('Code:   drush features-import will replace the active config with the displayed code.')));
+      $output->writeln(sprintf($green,
+        dt('Active: drush features-export will update the exported feature with the displayed active config')));
+
+      foreach ($overrides as $name) {
+        $message = '';
+        if (in_array($name, $missing)) {
+          $extension = [];
+          $message = sprintf($red, dt('(missing from active)'));
+        }
+        else {
+          $active = $manager->getActiveStorage()->read($name);
+          $extension = $manager->getExtensionStorages()->read($name);
+          if (empty($extension)) {
+            $extension = [];
+            $message = sprintf($green, dt('(not exported)'));
+          }
+          $diff = $config_diff->diff($extension, $active);
+          $rows = explode("\n", $formatter->format($diff));
+        }
+
+        $output->writeln('');
+        $output->writeln(dt("Config @name @message", [
+          '@name' => $name,
+          '@message' => $message,
+        ]));
+
+        if (!empty($extension)) {
+          foreach ($rows as $row) {
+            if (strpos($row, '>') === 0) {
+              $output->writeln(sprintf($green, $row));
+            }
+            elseif (strpos($row, '<') === 0) {
+              $output->writeln(sprintf($red, $row));
+            }
+            else {
+              $output->writeln($row);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Import a module config into your site.
+   *
+   * @param string $feature
+   *   A comma-delimited list of features or feature:component pairs to import.
+   *
+   * @command features:import
+   *
+   * @option force Force import even if config is not overridden.
+   * @option bundle Use a specific bundle namespace.
+   *
+   * @usage drush features-import foo:node.type.page
+   *   foo:taxonomy.vocabulary.tags bar Import node and taxonomy config of
+   *   feature "foo". Import all config of feature "bar".
+   *
+   * @aliases fim,fr,features-import
+   *
+   * @throws \Exception
+   */
+  public function import($feature, $options = self::OPTIONS_IMPORT) {
+    $this->featuresOptions($options);
+
+    $features = StringUtils::csvToArray($feature);
+    if (empty($features)) {
+      drush_invoke_process('@self', 'features:list:packages', [], $options);
+      return;
+    }
+
+    // Determine if revert should be forced.
+    $force = $this->getOption($options, 'force');
+
+    // Determine if -y was supplied. If so, we can filter out needless output
+    // from this command.
+    $skip_confirmation = drush_get_context('DRUSH_AFFIRMATIVE');
+    $manager = $this->manager;
+
+    // Parse list of arguments.
+    $modules = [];
+    foreach ($features as $featureString) {
+      list($module, $component) = explode(':', $featureString);
+
+      // We cannot use just a component name without its module.
+      if (empty($module)) {
+        continue;
+      }
+
+      // We received just a feature name, meaning we need all of its components.
+      if (empty($component)) {
+        $modules[$module] = TRUE;
+        continue;
+      }
+
+      if (empty($modules[$module])) {
+        $modules[$module] = [];
+      }
+
+      if ($modules[$module] !== TRUE) {
+        $modules[$module][] = $component;
+      }
+    }
+
+    // Process modules.
+    foreach ($modules as $module => $componentsNeeded) {
+      // Reset the arguments on each loop pass.
+      $dt_args = ['@module' => $module];
+
+      /** @var \Drupal\features\Package $feature */
+      $feature = $manager->loadPackage($module, TRUE);
+      if (empty($feature)) {
+        throw new DomainException(dt('No such feature is available: @module', $dt_args));
+      }
+
+      if ($feature->getStatus() != FeaturesManagerInterface::STATUS_INSTALLED) {
+        throw new DomainException(dt('No such feature is installed: @module', $dt_args));
+      }
+
+      // Forcefully revert all components of a feature.
+      if ($force) {
+        $components = $feature->getConfigOrig();
+      }
+      // Only revert components that are detected to be Overridden.
+      else {
+        $overrides = $manager->detectOverrides($feature);
+        $missing = $manager->reorderMissing($manager->detectMissing($feature));
+
+        // Be sure to import missing components first.
+        $components = array_merge($missing, $overrides);
+      }
+
+      if (!empty($componentsNeeded) && is_array($componentsNeeded)) {
+        $components = array_intersect($components, $componentsNeeded);
+      }
+
+      if (empty($components)) {
+        $this->logger()->info(dt('Current state already matches active config, aborting.'));
+        continue;
+      }
+
+      // Determine which config the user wants to import/revert.
+      $configToCreate = [];
+      foreach ($components as $component) {
+        $dt_args['@component'] = $component;
+        $confirmation_message = 'Do you really want to import @module : @component?';
+        if ($skip_confirmation || $this->io()->confirm(dt($confirmation_message, $dt_args))) {
+          $configToCreate[$component] = '';
+        }
+      }
+
+      // Perform the import/revert.
+      $importedConfig = $manager->createConfiguration($configToCreate);
+
+      // List the results.
+      foreach ($components as $component) {
+        $dt_args['@component'] = $component;
+        if (isset($importedConfig['new'][$component])) {
+          $this->logger()->info(dt('Imported @module : @component.', $dt_args));
+        }
+        elseif (isset($importedConfig['updated'][$component])) {
+          $this->logger()->info(dt('Reverted @module : @component.', $dt_args));
+        }
+        elseif (!isset($configToCreate[$component])) {
+          $this->logger()->info(dt('Skipping @module : @component.', $dt_args));
+        }
+        else {
+          $this->logger()->error(dt('Error importing @module : @component.', $dt_args));
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns an array of full config names given a array[$type][$component].
+   *
+   * @param array $items
+   *   The items to return data for.
+   *
+   * @return array
+   *   An array of config items.
+   */
+  protected function buildConfig(array $items) {
+    $result = [];
+    foreach ($items as $config_type => $item) {
+      foreach ($item as $item_name => $title) {
+        $result[] = $this->manager->getFullName($config_type, $item_name);
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * Returns a listing of all known components, indexed by source.
+   */
+  protected function componentList() {
+    $result = [];
+    $config = $this->manager->getConfigCollection();
+    foreach ($config as $item) {
+      $result[$item->getType()][$item->getShortName()] = $item->getLabel();
+    }
+    return $result;
+  }
+
+  /**
+   * Filters components by patterns.
+   */
+  protected function componentFilter($all_components, $patterns = [], $options = []) {
+    $options += [
+      'exported' => TRUE,
+      'not exported' => TRUE,
+      'provided by' => FALSE,
+    ];
+    $pool = [];
+    // Maps exported components to feature modules.
+    $components_map = $this->componentMap();
+    // First filter on exported state.
+    foreach ($all_components as $source => $components) {
+      foreach ($components as $name => $title) {
+        $exported = count($components_map[$source][$name]) > 0;
+        if ($exported) {
+          if ($options['exported']) {
+            $pool[$source][$name] = $title;
+          }
+        }
+        else {
+          if ($options['not exported']) {
+            $pool[$source][$name] = $title;
+          }
+        }
+      }
+    }
+
+    $state_string = '';
+
+    if (!$options['exported']) {
+      $state_string = 'unexported';
+    }
+    elseif (!$options['not exported']) {
+      $state_string = 'exported';
+    }
+
+    $selected = [];
+    foreach ($patterns as $pattern) {
+      // Rewrite * to %. Let users use both as wildcard.
+      $pattern = strtr($pattern, ['*' => '%']);
+      $sources = [];
+      list($source_pattern, $component_pattern) = explode(':', $pattern, 2);
+      // If source is empty, use a pattern.
+      if ($source_pattern == '') {
+        $source_pattern = '%';
+      }
+      if ($component_pattern == '') {
+        $component_pattern = '%';
+      }
+
+      $preg_source_pattern = strtr(preg_quote($source_pattern, '/'),
+        ['%' => '.*']);
+      $preg_component_pattern = strtr(preg_quote($component_pattern, '/'),
+        ['%' => '.*']);
+      // If it isn't a pattern, but a simple string, we don't anchor the
+      // pattern. This allows for abbreviating. Otherwise, we do, as this seems
+      // more natural for patterns.
+      if (strpos($source_pattern, '%') !== FALSE) {
+        $preg_source_pattern = '^' . $preg_source_pattern . '$';
+      }
+      if (strpos($component_pattern, '%') !== FALSE) {
+        $preg_component_pattern = '^' . $preg_component_pattern . '$';
+      }
+      $matches = [];
+
+      // Find the sources.
+      $all_sources = array_keys($pool);
+      $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources);
+      if (count($matches) > 0) {
+        // If we have multiple matches and the source string wasn't a
+        // pattern, check if one of the matches is equal to the pattern, and
+        // use that, or error out.
+        if (count($matches) > 1 and $preg_source_pattern[0] != '^') {
+          if (in_array($source_pattern, $matches)) {
+            $matches = [$source_pattern];
+          }
+          else {
+            throw new \Exception(dt('Ambiguous source "@source", matches @matches',
+              [
+                '@source' => $source_pattern,
+                '@matches' => implode(', ', $matches),
+              ]));
+          }
+        }
+        // Loose the indexes preg_grep preserved.
+        $sources = array_values($matches);
+      }
+      else {
+        throw new \Exception(dt('No @state sources match "@source"',
+          ['@state' => $state_string, '@source' => $source_pattern]));
+      }
+
+      // Now find the components.
+      foreach ($sources as $source) {
+        // Find the components.
+        $all_components = array_keys($pool[$source]);
+        // See if there's any matches.
+        $matches = preg_grep('/' . $preg_component_pattern . '/',
+          $all_components);
+        if (count($matches) > 0) {
+          // If we have multiple matches and the components string wasn't a
+          // pattern, check if one of the matches is equal to the pattern, and
+          // use that, or error out.
+          if (count($matches) > 1 and $preg_component_pattern[0] != '^') {
+            if (in_array($component_pattern, $matches)) {
+              $matches = [$component_pattern];
+            }
+            else {
+              throw new \Exception(dt('Ambiguous component "@component", matches @matches',
+                [
+                  '@component' => $component_pattern,
+                  '@matches' => implode(', ', $matches),
+                ]));
+            }
+          }
+          if (!is_array($selected[$source])) {
+            $selected[$source] = [];
+          }
+          $selected[$source] += array_intersect_key($pool[$source],
+            array_flip($matches));
+        }
+        else {
+          // No matches. If the source was a pattern, just carry on, else
+          // error out. Allows for patterns like ":*field*".
+          if ($preg_source_pattern[0] != '^') {
+            throw new \Exception(dt('No @state @source components match "@component"',
+              [
+                '@state' => $state_string,
+                '@component' => $component_pattern,
+                '@source' => $source,
+              ]));
+          }
+        }
+      }
+    }
+
+    // Lastly, provide feature module information on the selected components, if
+    // requested.
+    $provided_by = [];
+    if ($options['provided by'] && $options['exported']) {
+      foreach ($selected as $source => $components) {
+        foreach ($components as $name => $title) {
+          $exported = count($components_map[$source][$name]) > 0;
+          if ($exported) {
+            $provided_by[$source . ':' . $name] = implode(', ',
+              $components_map[$source][$name]);
+          }
+        }
+      }
+    }
+
+    return [
+      'components' => $selected,
+      'sources' => $provided_by,
+    ];
+  }
+
+  /**
+   * Provides a component to feature map (port of features_get_component_map).
+   */
+  protected function componentMap() {
+    $result = [];
+    $manager = $this->manager;
+    // Recalc full config list without running assignments.
+    $config = $manager->getConfigCollection();
+    $packages = $manager->getPackages();
+
+    foreach ($config as $item) {
+      $type = $item->getType();
+      $short_name = $item->getShortName();
+      if (!isset($result[$type][$short_name])) {
+        $result[$type][$short_name] = [];
+      }
+      if (!empty($item->getPackage())) {
+        $package = $packages[$item->getPackage()];
+        $result[$type][$short_name][] = $package->getMachineName();
+      }
+    }
+
+    return $result;
+  }
+
+  /**
+   * Prints a list of filtered components.
+   */
+  protected function componentPrint($filtered_components) {
+    $rows = [];
+    foreach ($filtered_components['components'] as $source => $components) {
+      foreach ($components as $name => $value) {
+        $row = ['source' => $source . ':' . $name];
+        if (isset($filtered_components['sources'][$source . ':' . $name])) {
+          $row['source'] = dt('Provided by') . ': ' . $filtered_components['sources'][$source . ':' . $name];
+        }
+        $rows[] = $row;
+      }
+    }
+
+    return new RowsOfFields($rows);
+  }
+
+}

+ 7 - 0
sites/all/modules/contrib/admin/features/src/Exception/DomainException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Drupal\features\Exception;
+
+class DomainException extends \DomainException {
+
+}

+ 7 - 0
sites/all/modules/contrib/admin/features/src/Exception/InvalidArgumentException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Drupal\features\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException {
+
+}

+ 1 - 2
sites/all/modules/contrib/admin/features/src/FeaturesAssigner.php

@@ -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']);
     }
 
   }

+ 1 - 1
sites/all/modules/contrib/admin/features/src/FeaturesConfigInstaller.php

@@ -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 {

+ 9 - 1
sites/all/modules/contrib/admin/features/src/FeaturesInstallStorage.php

@@ -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);
+    }
   }
 
   /**

+ 36 - 20
sites/all/modules/contrib/admin/features/src/FeaturesManager.php

@@ -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]));
                 }
               }

+ 14 - 6
sites/all/modules/contrib/admin/features/src/Package.php

@@ -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
sites/all/modules/contrib/admin/features/src/Plugin/FeaturesAssignment/FeaturesAssignmentPackages.php

@@ -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
sites/all/modules/contrib/admin/features/tests/modules/test_feature/test_feature.info.yml

@@ -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 - 0
sites/all/modules/contrib/admin/features/tests/modules/test_mybundle_core/test_mybundle_core.features.yml

@@ -1,3 +1,5 @@
 bundle: test_mybundle
 excluded:
   - system.theme
+required:
+  - core.date_format.long

+ 3 - 3
sites/all/modules/contrib/admin/features/tests/modules/test_mybundle_core/test_mybundle_core.info.yml

@@ -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

+ 22 - 1
sites/all/modules/contrib/admin/features/tests/src/Kernel/FeaturesAssignTest.php

@@ -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');

+ 200 - 9
sites/all/modules/contrib/admin/features/tests/src/Unit/FeaturesManagerTest.php

@@ -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', '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_config', 'test_config2'], $this->featuresManager->getPackage('test_package')->getConfig());
-    $this->assertEquals(['example', 'my_module'], $this->featuresManager->getPackage('test_package')->getDependencies());
+    $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']);
   }
 
   /**

+ 1 - 1
sites/all/modules/contrib/dev/entity/.travis.yml

@@ -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

+ 1 - 1
sites/all/modules/contrib/dev/entity/composer.json

@@ -5,6 +5,6 @@
   "homepage": "http://drupal.org/project/entity",
   "license": "GPL-2.0+",
   "require": {
-    "drupal/core": "~8.3"
+    "drupal/core": "~8.5"
   }
 }

+ 4 - 4
sites/all/modules/contrib/dev/entity/entity.info.yml

@@ -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

+ 2 - 0
sites/all/modules/contrib/dev/entity/entity.links.action.yml

@@ -0,0 +1,2 @@
+entity.entity_actions:
+  deriver: Drupal\entity\Plugin\Derivative\EntityActionsDeriver

+ 6 - 0
sites/all/modules/contrib/dev/entity/entity.services.yml

@@ -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']

+ 87 - 0
sites/all/modules/contrib/dev/entity/src/Access/EntityDeleteMultipleAccessCheck.php

@@ -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();
+  }
+
+}

+ 6 - 14
sites/all/modules/contrib/dev/entity/src/Controller/RevisionControllerTrait.php

@@ -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()) {

+ 0 - 17
sites/all/modules/contrib/dev/entity/src/Entity/RevisionableEntityBundleInterface.php

@@ -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();
-
-}

+ 0 - 6
sites/all/modules/contrib/dev/entity/src/EntityPermissionProvider.php

@@ -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.

+ 0 - 37
sites/all/modules/contrib/dev/entity/src/EntityViewBuilder.php

@@ -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(),
-        ],
-      ];
-    }
-  }
-
-}

+ 0 - 226
sites/all/modules/contrib/dev/entity/src/Form/DeleteMultiple.php

@@ -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());
-  }
-
-}

+ 323 - 0
sites/all/modules/contrib/dev/entity/src/Form/DeleteMultipleForm.php

@@ -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.");
+  }
+
+}

+ 0 - 159
sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php

@@ -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();
-    }
-  }
-
-}

+ 81 - 0
sites/all/modules/contrib/dev/entity/src/Menu/EntityAddLocalAction.php

@@ -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(),
+    ]);
+  }
+
+}

+ 41 - 0
sites/all/modules/contrib/dev/entity/src/Menu/EntityCollectionLocalActionProvider.php

@@ -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;
+  }
+
+}

+ 23 - 0
sites/all/modules/contrib/dev/entity/src/Menu/EntityLocalActionProviderInterface.php

@@ -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);
+
+}

+ 6 - 6
sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php

@@ -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
sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php

@@ -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;

+ 58 - 0
sites/all/modules/contrib/dev/entity/src/Plugin/Derivative/EntityActionsDeriver.php

@@ -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;
+  }
+
+}

+ 1 - 5
sites/all/modules/contrib/dev/entity/src/Revision/RevisionableContentEntityBase.php

@@ -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();
     }

+ 26 - 0
sites/all/modules/contrib/dev/entity/src/Routing/AdminHtmlRouteProvider.php

@@ -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;
+  }
+
+}

+ 26 - 0
sites/all/modules/contrib/dev/entity/src/Routing/DefaultHtmlRouteProvider.php

@@ -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;
+  }
+
+}

+ 2 - 2
sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php

@@ -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;
     }

+ 1 - 6
sites/all/modules/contrib/dev/entity/src/UncacheableEntityPermissionProvider.php

@@ -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
sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_examples_test/entity_module_bundle_plugin_examples_test.info.yml

@@ -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
sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_test/entity_module_bundle_plugin_test.info.yml

@@ -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
sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml

@@ -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

+ 0 - 7
sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.routing.yml

@@ -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
sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php

@@ -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
sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php

@@ -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
sites/all/modules/contrib/dev/entity/tests/src/Functional/CollectionRouteAccessTest.php

@@ -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
sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php

@@ -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
sites/all/modules/contrib/dev/entity/tests/src/Functional/Menu/EntityLocalActionTest.php

@@ -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
sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php

@@ -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');

+ 2 - 2
sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php

@@ -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 changed in this diff